diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 04345d7cb5..57270668c0 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -209,3 +209,4 @@ under this name, or we did not manage to find their commits in the history. - correctmost <134317971+correctmost@users.noreply.github.com> - Oleh Prypin - Eric Vergnaud +- Hashem Nasarat diff --git a/ChangeLog b/ChangeLog index d2c93f1abd..0651f5aa15 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,7 +9,7 @@ Release date: TBA -What's New in astroid 3.3.3? +What's New in astroid 3.3.4? ============================ Release date: TBA @@ -18,6 +18,19 @@ Release date: TBA Closes pylint-dev/pylint#9811 +What's New in astroid 3.3.3? +============================ +Release date: 2024-09-20 + +* Fix inference regression with property setters. + + Closes pylint-dev/pylint#9811 + +* Add annotation-only instance attributes to attrs classes to fix `no-member` false positives. + + Closes #2514 + + What's New in astroid 3.3.2? ============================ Release date: 2024-08-11 diff --git a/astroid/brain/brain_attrs.py b/astroid/brain/brain_attrs.py index b7a7eafe1b..23ec9f66a4 100644 --- a/astroid/brain/brain_attrs.py +++ b/astroid/brain/brain_attrs.py @@ -24,6 +24,13 @@ "field", ) ) +NEW_ATTRS_NAMES = frozenset( + ( + "attrs.define", + "attrs.mutable", + "attrs.frozen", + ) +) ATTRS_NAMES = frozenset( ( "attr.s", @@ -33,9 +40,7 @@ "attr.define", "attr.mutable", "attr.frozen", - "attrs.define", - "attrs.mutable", - "attrs.frozen", + *NEW_ATTRS_NAMES, ) ) @@ -64,13 +69,14 @@ def attr_attributes_transform(node: ClassDef) -> None: # Prevents https://github.com/pylint-dev/pylint/issues/1884 node.locals["__attrs_attrs__"] = [Unknown(parent=node)] + use_bare_annotations = is_decorated_with_attrs(node, NEW_ATTRS_NAMES) for cdef_body_node in node.body: if not isinstance(cdef_body_node, (Assign, AnnAssign)): continue if isinstance(cdef_body_node.value, Call): if cdef_body_node.value.func.as_string() not in ATTRIB_NAMES: continue - else: + elif not use_bare_annotations: continue targets = ( cdef_body_node.targets diff --git a/script/.contributors_aliases.json b/script/.contributors_aliases.json index 73e9e0db14..1dc38a4870 100644 --- a/script/.contributors_aliases.json +++ b/script/.contributors_aliases.json @@ -22,6 +22,10 @@ "mails": ["55152140+jayaddison@users.noreply.github.com", "jay@jp-hosting.net"], "name": "James Addison" }, + "Hnasar@users.noreply.github.com": { + "mails": ["Hnasar@users.noreply.github.com", "hashem@hudson-trading.com"], + "name": "Hashem Nasarat" + }, "adam.grant.hendry@gmail.com": { "mails": ["adam.grant.hendry@gmail.com"], "name": "Adam Hendry" diff --git a/tests/brain/test_attr.py b/tests/brain/test_attr.py index e428b0c8d2..ef4887378f 100644 --- a/tests/brain/test_attr.py +++ b/tests/brain/test_attr.py @@ -7,7 +7,7 @@ import unittest import astroid -from astroid import nodes +from astroid import exceptions, nodes try: import attr # type: ignore[import] # pylint: disable=unused-import @@ -201,3 +201,31 @@ class Foo: """ should_be_unknown = next(astroid.extract_node(code).infer()).getattr("bar")[0] self.assertIsInstance(should_be_unknown, astroid.Unknown) + + def test_attr_with_only_annotation_fails(self) -> None: + code = """ + import attr + + @attr.s + class Foo: + bar: int + Foo() + """ + with self.assertRaises(exceptions.AttributeInferenceError): + next(astroid.extract_node(code).infer()).getattr("bar") + + def test_attrs_with_only_annotation_works(self) -> None: + code = """ + import attrs + + @attrs.define + class Foo: + bar: int + baz: str = "hello" + Foo(1) + """ + for attr_name in ("bar", "baz"): + should_be_unknown = next(astroid.extract_node(code).infer()).getattr( + attr_name + )[0] + self.assertIsInstance(should_be_unknown, astroid.Unknown)