diff --git a/conformance/third_party/conformance.exp b/conformance/third_party/conformance.exp index 9b3369058..8b6b2db41 100644 --- a/conformance/third_party/conformance.exp +++ b/conformance/third_party/conformance.exp @@ -7455,6 +7455,16 @@ "stop_column": 13, "stop_line": 59 }, + { + "code": -2, + "column": 5, + "concise_description": "Cannot override named tuple element `x`", + "description": "Cannot override named tuple element `x`", + "line": 79, + "name": "bad-override", + "stop_column": 6, + "stop_line": 79 + }, { "code": -2, "column": 19, diff --git a/conformance/third_party/conformance.result b/conformance/third_party/conformance.result index 3d53a5019..ccbdde7b6 100644 --- a/conformance/third_party/conformance.result +++ b/conformance/third_party/conformance.result @@ -373,9 +373,7 @@ ], "literals_parameterizations.py": [], "literals_semantics.py": [], - "namedtuples_define_class.py": [ - "Line 79: Expected 1 errors" - ], + "namedtuples_define_class.py": [], "namedtuples_define_functional.py": [], "namedtuples_type_compat.py": [], "namedtuples_usage.py": [], diff --git a/conformance/third_party/results.json b/conformance/third_party/results.json index 0883204aa..6e8d891ee 100644 --- a/conformance/third_party/results.json +++ b/conformance/third_party/results.json @@ -1,9 +1,9 @@ { "total": 136, - "pass": 60, - "fail": 76, - "pass_rate": 0.44, - "differences": 356, + "pass": 61, + "fail": 75, + "pass_rate": 0.45, + "differences": 355, "passing": [ "aliases_explicit.py", "aliases_newtype.py", @@ -49,6 +49,7 @@ "historical_positional.py", "literals_parameterizations.py", "literals_semantics.py", + "namedtuples_define_class.py", "namedtuples_define_functional.py", "namedtuples_type_compat.py", "namedtuples_usage.py", @@ -114,7 +115,6 @@ "generics_variance.py": 12, "literals_interactions.py": 2, "literals_literalstring.py": 3, - "namedtuples_define_class.py": 1, "narrowing_typeguard.py": 2, "narrowing_typeis.py": 4, "overloads_consistency.py": 2, diff --git a/pyrefly/lib/alt/class/class_field.rs b/pyrefly/lib/alt/class/class_field.rs index 25a55c78f..8bc901b6d 100644 --- a/pyrefly/lib/alt/class/class_field.rs +++ b/pyrefly/lib/alt/class/class_field.rs @@ -1201,6 +1201,18 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { for (parent, parent_metadata) in parents { parent_has_any = parent_has_any || parent_metadata.has_base_any(); + // Don't allow overriding a namedtuple element + if let Some(named_tuple_metadata) = parent_metadata.named_tuple_metadata() { + if named_tuple_metadata.elements.contains(name) { + self.error( + errors, + range, + ErrorKind::BadOverride, + None, + format!("Cannot override named tuple element `{}`", name), + ); + } + } let Some(want_member) = self.get_class_member(parent.class_object(), name) else { continue; }; diff --git a/pyrefly/lib/test/named_tuple.rs b/pyrefly/lib/test/named_tuple.rs index 27c744878..c60fa1815 100644 --- a/pyrefly/lib/test/named_tuple.rs +++ b/pyrefly/lib/test/named_tuple.rs @@ -256,3 +256,20 @@ class Foo(NamedTuple): y: str # E: NamedTuple field 'y' without a default may not follow NamedTuple field with a default "#, ); + +testcase!( + test_named_tuple_override_error, + r#" +from typing import NamedTuple + +class A(NamedTuple): + x: int + +class B(A): + x: int # E: Cannot override named tuple element `x` + y: int + +class C(B): + y: int # OK +"#, +);