diff options
author | Eric V. Smith <ericvsmith@users.noreply.github.com> | 2018-03-22 20:28:48 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-03-22 20:28:48 (GMT) |
commit | 56970b8ce9d23269d20a76f13c80e670c856ba7f (patch) | |
tree | 53012d578fa3d64d9919453bb817e89a94227198 /Lib/dataclasses.py | |
parent | f757b72b2524ce3451d2269f0b8a9f0593a7b27f (diff) | |
download | cpython-56970b8ce9d23269d20a76f13c80e670c856ba7f.zip cpython-56970b8ce9d23269d20a76f13c80e670c856ba7f.tar.gz cpython-56970b8ce9d23269d20a76f13c80e670c856ba7f.tar.bz2 |
bpo-32505: dataclasses: raise TypeError if a member variable is of type Field, but doesn't have a type annotation. (GH-6192)
If a dataclass has a member variable that's of type Field, but it doesn't have a type annotation, raise TypeError.
Diffstat (limited to 'Lib/dataclasses.py')
-rw-r--r-- | Lib/dataclasses.py | 38 |
1 files changed, 21 insertions, 17 deletions
diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 41b5b5d..5d4d4a6 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -573,22 +573,6 @@ def _get_field(cls, a_name, a_type): return f -def _find_fields(cls): - # Return a list of Field objects, in order, for this class (and no - # base classes). Fields are found from the class dict's - # __annotations__ (which is guaranteed to be ordered). Default - # values are from class attributes, if a field has a default. If - # the default value is a Field(), then it contains additional - # info beyond (and possibly including) the actual default value. - # Pseudo-fields ClassVars and InitVars are included, despite the - # fact that they're not real fields. That's dealt with later. - - # If __annotations__ isn't present, then this class adds no new - # annotations. - annotations = cls.__dict__.get('__annotations__', {}) - return [_get_field(cls, name, type) for name, type in annotations.items()] - - def _set_new_attribute(cls, name, value): # Never overwrites an existing attribute. Returns True if the # attribute already exists. @@ -663,10 +647,25 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen): if getattr(b, _PARAMS).frozen: any_frozen_base = True + # Annotations that are defined in this class (not in base + # classes). If __annotations__ isn't present, then this class + # adds no new annotations. We use this to compute fields that + # are added by this class. + # Fields are found from cls_annotations, which is guaranteed to be + # ordered. Default values are from class attributes, if a field + # has a default. If the default value is a Field(), then it + # contains additional info beyond (and possibly including) the + # actual default value. Pseudo-fields ClassVars and InitVars are + # included, despite the fact that they're not real fields. + # That's dealt with later. + cls_annotations = cls.__dict__.get('__annotations__', {}) + # Now find fields in our class. While doing so, validate some # things, and set the default values (as class attributes) # where we can. - for f in _find_fields(cls): + cls_fields = [_get_field(cls, name, type) + for name, type in cls_annotations.items()] + for f in cls_fields: fields[f.name] = f # If the class attribute (which is the default value for @@ -685,6 +684,11 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen): else: setattr(cls, f.name, f.default) + # Do we have any Field members that don't also have annotations? + for name, value in cls.__dict__.items(): + if isinstance(value, Field) and not name in cls_annotations: + raise TypeError(f'{name!r} is a field but has no type annotation') + # Check rules that apply if we are derived from any dataclasses. if has_dataclass_bases: # Raise an exception if any of our bases are frozen, but we're not. |