Skip to content

Commit

Permalink
handle named tuple in base class list
Browse files Browse the repository at this point in the history
Summary: This diff eliminates some of the special casing of named tuple base classes and bans multiple inheritance for named tuples. This diff does not implement any other typechecking functionality for named tuples.

Reviewed By: ndmitchell

Differential Revision: D68250607

fbshipit-source-id: 418b45f773a2e1d45c56eda123abdaf8f68f06d2
  • Loading branch information
yangdanny97 authored and facebook-github-bot committed Jan 24, 2025
1 parent f8452b6 commit a146a38
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 18 deletions.
10 changes: 10 additions & 0 deletions pyre2/conformance/third_party/conformance.exp
Original file line number Diff line number Diff line change
Expand Up @@ -15589,6 +15589,16 @@
"name": "PyreError",
"stop_column": 14,
"stop_line": 98
},
{
"code": -2,
"column": 7,
"concise_description": "Named tuples do not support multiple inheritance",
"description": "Named tuples do not support multiple inheritance",
"line": 105,
"name": "PyreError",
"stop_column": 11,
"stop_line": 105
}
],
"namedtuples_define_functional.py": [
Expand Down
1 change: 0 additions & 1 deletion pyre2/conformance/third_party/conformance.result
Original file line number Diff line number Diff line change
Expand Up @@ -1083,7 +1083,6 @@
"namedtuples_define_class.py": [
"Line 59: Expected 1 errors",
"Line 79: Expected 1 errors",
"Line 105: Expected 1 errors",
"Line 21: Unexpected errors ['Expected `__init__` to be a callable, got Callable[[NamedTuple, str, Iterable[tuple[str, Any]]], None] | Callable[[NamedTuple, str, None, KwArgs[Any]], None]']",
"Line 23: Unexpected errors ['assert_type(Any, int) failed', '`Point` has no attribute `__getitem__`']",
"Line 24: Unexpected errors ['assert_type(Any, int) failed', '`Point` has no attribute `__getitem__`']",
Expand Down
4 changes: 2 additions & 2 deletions pyre2/conformance/third_party/results.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"pass": 9,
"fail": 124,
"pass_rate": 0.07,
"differences": 1345,
"differences": 1344,
"passing": [
"annotations_coroutines.py",
"directives_no_type_check.py",
Expand Down Expand Up @@ -99,7 +99,7 @@
"literals_literalstring.py": 14,
"literals_parameterizations.py": 6,
"literals_semantics.py": 2,
"namedtuples_define_class.py": 21,
"namedtuples_define_class.py": 20,
"namedtuples_define_functional.py": 17,
"namedtuples_type_compat.py": 4,
"namedtuples_usage.py": 13,
Expand Down
21 changes: 17 additions & 4 deletions pyre2/pyre2/bin/alt/classes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,6 @@ impl Attribute {
/// work of computing inherticance information and the MRO.
#[derive(Debug, Clone)]
enum BaseClass {
#[expect(dead_code)] // Will be used in the future
NamedTuple,
TypedDict,
Generic(Vec<Type>),
Protocol(Vec<Type>),
Expand Down Expand Up @@ -280,7 +278,7 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {

fn base_class_of(&self, base_expr: &Expr) -> BaseClass {
if let Some(special_base_class) = self.special_base_class(base_expr) {
// This branch handles cases like `NamedTuple` or `Protocol`
// This branch handles cases like `Protocol`
special_base_class
} else if let Expr::Subscript(subscript) = base_expr
&& let Some(mut special_base_class) = self.special_base_class(&subscript.value)
Expand Down Expand Up @@ -395,15 +393,23 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
keywords: &[(Name, Expr)],
) -> ClassMetadata {
let mut is_typed_dict = false;
let mut is_named_tuple = false;
let bases_with_metadata: Vec<_> = bases
.iter()
.filter_map(|x| match self.base_class_of(x) {
BaseClass::Expr(x) => match self.expr_untype(&x) {
Type::ClassType(c) => {
let class_metadata = self.get_metadata_for_class(c.class_object());
let cls = c.class_object();
let class_metadata = self.get_metadata_for_class(cls);
if class_metadata.is_typed_dict() {
is_typed_dict = true;
}
if class_metadata.is_named_tuple()
|| cls.qname().module.name().as_str() == "typing"
&& cls.qname().name.id == "NamedTuple"
{
is_named_tuple = true;
}
Some((c, class_metadata))
}
Type::TypedDict(typed_dict) => {
Expand All @@ -427,6 +433,12 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
_ => None,
})
.collect();
if is_named_tuple && bases_with_metadata.len() > 1 {
self.error(
cls.name().range,
"Named tuples do not support multiple inheritance".to_owned(),
);
}
let (metaclasses, keywords): (Vec<_>, Vec<(_, _)>) =
keywords.iter().partition_map(|(n, x)| match n.as_str() {
"metaclass" => Either::Left(x),
Expand All @@ -446,6 +458,7 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
metaclass,
keywords,
is_typed_dict,
is_named_tuple,
self.errors(),
)
}
Expand Down
1 change: 1 addition & 0 deletions pyre2/pyre2/bin/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mod imports;
mod legacy_generic;
mod lsp;
mod mro;
mod named_tuple;
mod narrow;
mod operators;
mod overload;
Expand Down
34 changes: 34 additions & 0 deletions pyre2/pyre2/bin/test/named_tuple.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

use crate::testcase;

testcase!(
test_named_tuple,
r#"
from typing import NamedTuple
class Pair(NamedTuple):
x: int
y: int
"#,
);

testcase!(
test_named_tuple_multiple_inheritance,
r#"
from typing import NamedTuple
class Foo: pass
class Pair(NamedTuple, Foo): # E: Named tuples do not support multiple inheritance
x: int
y: int
class Pair2(NamedTuple):
x: int
y: int
class Pair3(Pair2, Foo): # E: Named tuples do not support multiple inheritance
pass
"#,
);
10 changes: 0 additions & 10 deletions pyre2/pyre2/bin/test/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,3 @@ for x in f():
assert_type(x, int | bool | str)
"#,
);

testcase!(
test_named_tuple,
r#"
from typing import NamedTuple
class Pair(NamedTuple):
x: int
y: int
"#,
);
10 changes: 9 additions & 1 deletion pyre2/pyre2/bin/types/class_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub struct ClassMetadata {
metaclass: Metaclass,
keywords: Keywords,
is_typed_dict: bool,
is_named_tuple: bool,
}

impl Display for ClassMetadata {
Expand All @@ -43,13 +44,15 @@ impl ClassMetadata {
metaclass: Option<ClassType>,
keywords: Vec<(Name, Type)>,
is_typed_dict: bool,
is_named_tuple: bool,
errors: &ErrorCollector,
) -> ClassMetadata {
ClassMetadata {
mro: Mro::new(cls, bases_with_metadata, errors),
metaclass: Metaclass(metaclass),
keywords: Keywords(keywords),
is_typed_dict,
is_named_tuple,
}
}

Expand All @@ -59,6 +62,7 @@ impl ClassMetadata {
metaclass: Metaclass::default(),
keywords: Keywords::default(),
is_typed_dict: false,
is_named_tuple: false,
}
}

Expand All @@ -84,6 +88,10 @@ impl ClassMetadata {
self.is_typed_dict
}

pub fn is_named_tuple(&self) -> bool {
self.is_named_tuple
}

pub fn ancestors<'a>(&'a self, stdlib: &'a Stdlib) -> impl Iterator<Item = &'a ClassType> {
self.ancestors_no_object()
.iter()
Expand Down Expand Up @@ -188,7 +196,7 @@ impl Mro {
/// https://en.wikipedia.org/wiki/C3_linearization
///
/// TODO: We currently omit some classes that are in the runtime MRO:
/// `Generic`, `Protocol`, `NamedTuple`, and `object`.
/// `Generic`, `Protocol`, and `object`.
pub fn new(
cls: &Class,
bases_with_metadata: Vec<(ClassType, Arc<ClassMetadata>)>,
Expand Down

0 comments on commit a146a38

Please sign in to comment.