Skip to content

Commit 9f4560d

Browse files
yangdanny97facebook-github-bot
authored andcommitted
improve NamedTuple field checks
Summary: Based on some testing in the CLI - methods are never counted as fields (but lambdas work, unlike enums) - it's a runtime error if named tuple fields start with an underscore, with some caveats - if it's a method, then it's not a field so there's no runtime error if it starts with underscore. - if a field is renamed to _0, _1 using the functional syntax rename=True param, then there's no error if it starts with underscore - if a user-written field starts with an underscore and rename=True is set, it will be renamed to _0, _1, etc. This diff implements all of the above functionality, EXCEPT disallowing fields starting with underscore for the class syntax. This is because we don't have a way to differentiate a renamed field from a field originally written by person. I think we could probably do something about this by moving every field on a synthesized class into the same structure that we're using for synthesized methods, but that's a bigger change for a separate diff. For now, I left a testcase with some comments. fixes #101 Reviewed By: stroxler Differential Revision: D73113784 fbshipit-source-id: 2f20e6b949e2e17dbfedf36f153d333cae98a786
1 parent 872db5f commit 9f4560d

File tree

3 files changed

+39
-1
lines changed

3 files changed

+39
-1
lines changed

Diff for: pyrefly/lib/alt/class/named_tuple.rs

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
3030
pub fn get_named_tuple_elements(&self, cls: &Class) -> Vec<Name> {
3131
let mut elements = Vec::new();
3232
for name in cls.fields() {
33+
if !cls.is_field_annotated(name) {
34+
continue;
35+
}
3336
if let Some(range) = cls.field_decl_range(name) {
3437
elements.push((name.clone(), range));
3538
}

Diff for: pyrefly/lib/binding/class.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -388,9 +388,13 @@ impl<'a> BindingsBuilder<'a> {
388388
);
389389
continue;
390390
}
391+
// Synthesized fields for named tuples are always considered annotated
391392
fields.insert(
392393
member_name.clone(),
393-
ClassFieldProperties::new(member_annotation.is_some(), range),
394+
ClassFieldProperties::new(
395+
member_annotation.is_some() || class_kind == SynthesizedClassKind::NamedTuple,
396+
range,
397+
),
394398
);
395399
let initial_value = if force_class_initialization || member_value.is_some() {
396400
ClassFieldInitialValue::Class(member_value.clone())

Diff for: pyrefly/lib/test/named_tuple.rs

+31
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,34 @@ x = 2
197197
Tup = namedtuple("Tup", ["a", "b"], defaults=(None, x))
198198
"#,
199199
);
200+
201+
testcase!(
202+
test_named_tuple_dunder_unpack,
203+
r#"
204+
from typing import NamedTuple
205+
class A(NamedTuple):
206+
a: int
207+
b: str
208+
def __repr__(self) -> str:
209+
return "A"
210+
211+
def test(x: A) -> None:
212+
a, b = x
213+
"#,
214+
);
215+
216+
testcase!(
217+
bug =
218+
"Field names cannot start with an underscore, unless they were generated with rename=True",
219+
test_named_tuple_underscore_field_name,
220+
r#"
221+
from typing import NamedTuple
222+
from collections import namedtuple
223+
class A(NamedTuple):
224+
a: int
225+
b: str
226+
_c: str # Not OK
227+
B = namedtuple("B", ["a", "b", "_c"]) # E: NamedTuple field name may not start with an underscore
228+
C = namedtuple("C", ["a", "b", "_c"], rename=True) # OK
229+
"#,
230+
);

0 commit comments

Comments
 (0)