Skip to content

Commit f4ddc2c

Browse files
oopscompiledmeta-codesync[bot]
authored andcommitted
fix class_metadata and regression test (#2379)
Summary: This PR addresses issue #2371 by reporting inconsistent type args when the same direct base class appears with different type parameters across inheritance paths. It also adds a regression test Fixes #2371 Pull Request resolved: #2379 Test Plan: python3 test.py Reviewed By: stroxler Differential Revision: D92869958 Pulled By: migeed-z fbshipit-source-id: f282f8b53700cea7db503f9c269bdaeadc806eba
1 parent 768ae53 commit f4ddc2c

File tree

7 files changed

+83
-32
lines changed

7 files changed

+83
-32
lines changed

conformance/third_party/conformance.exp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5322,6 +5322,17 @@
53225322
"severity": "error",
53235323
"stop_column": 29,
53245324
"stop_line": 68
5325+
},
5326+
{
5327+
"code": -2,
5328+
"column": 7,
5329+
"concise_description": "Class `BadChild` has inconsistent type arguments for base class `Grandparent`: `Grandparent[T1, T2]` and `Grandparent[T2, T1]`",
5330+
"description": "Class `BadChild` has inconsistent type arguments for base class `Grandparent`: `Grandparent[T1, T2]` and `Grandparent[T2, T1]`",
5331+
"line": 98,
5332+
"name": "invalid-inheritance",
5333+
"severity": "error",
5334+
"stop_column": 15,
5335+
"stop_line": 98
53255336
}
53265337
],
53275338
"generics_basic.py": [

conformance/third_party/conformance.result

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,7 @@
9494
"Line 50: Unexpected errors ['assert_type(str, int | str) failed']",
9595
"Line 57: Unexpected errors ['assert_type(str, int | str) failed']"
9696
],
97-
"generics_base_class.py": [
98-
"Line 98: Expected 1 errors"
99-
],
97+
"generics_base_class.py": [],
10098
"generics_basic.py": [
10199
"Line 34: Unexpected errors ['`+` is not supported between `AnyStr` and `AnyStr`\\n Argument `AnyStr` is not assignable to parameter `value` with type `Buffer` in function `bytes.__add__`\\n Protocol `Buffer` requires attribute `__buffer__`', '`+` is not supported between `AnyStr` and `AnyStr`\\n No matching overload found for function `str.__add__` called with arguments: (AnyStr)\\n Possible overloads:\\n (value: LiteralString, /) -> LiteralString\\n (value: str, /) -> str [closest match]']",
102100
"Line 67: Unexpected errors ['assert_type(MyStr, str) failed']",

conformance/third_party/results.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"total": 138,
3-
"pass": 114,
4-
"fail": 24,
3+
"pass": 115,
4+
"fail": 23,
55
"pass_rate": 0.83,
6-
"differences": 89,
6+
"differences": 88,
77
"passing": [
88
"aliases_explicit.py",
99
"aliases_newtype.py",
@@ -54,6 +54,7 @@
5454
"enums_member_names.py",
5555
"enums_member_values.py",
5656
"enums_members.py",
57+
"generics_base_class.py",
5758
"generics_defaults_referential.py",
5859
"generics_defaults_specialization.py",
5960
"generics_paramspec_basic.py",
@@ -129,7 +130,6 @@
129130
"dataclasses_descriptors.py": 6,
130131
"dataclasses_slots.py": 2,
131132
"exceptions_context_managers.py": 2,
132-
"generics_base_class.py": 1,
133133
"generics_basic.py": 3,
134134
"generics_defaults.py": 1,
135135
"generics_paramspec_components.py": 1,

pyrefly/lib/alt/types/class_bases.rs

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -350,27 +350,10 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
350350
"Second argument to NewType cannot be an unbound generic".to_owned(),
351351
);
352352
}
353-
if let Some(base_tuple_ancestor) = base_class_bases.tuple_ancestor() {
354-
if let Some(existing_tuple_ancestor) = &tuple_ancestor {
355-
if existing_tuple_ancestor.is_any_tuple() {
356-
tuple_ancestor = Some(base_tuple_ancestor.clone());
357-
} else if !base_tuple_ancestor.is_any_tuple()
358-
&& base_tuple_ancestor != existing_tuple_ancestor
359-
{
360-
self.error(
361-
errors,
362-
range,
363-
ErrorInfo::Kind(ErrorKind::InvalidInheritance),
364-
format!(
365-
"Cannot extend multiple incompatible tuples: `{}` and `{}`",
366-
self.for_display(Type::Tuple(existing_tuple_ancestor.clone())),
367-
self.for_display(Type::Tuple(base_tuple_ancestor.clone())),
368-
),
369-
);
370-
}
371-
} else {
372-
tuple_ancestor = Some(base_tuple_ancestor.clone());
373-
}
353+
if let Some(base_tuple_ancestor) = base_class_bases.tuple_ancestor()
354+
&& tuple_ancestor.as_ref().is_none_or(|t| t.is_any_tuple())
355+
{
356+
tuple_ancestor = Some(base_tuple_ancestor.clone());
374357
}
375358
(base_class_type, range)
376359
})

pyrefly/lib/alt/types/class_metadata.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ impl Linearization {
621621
Err(_) => return Linearization::empty(),
622622
};
623623
let mut ancestor_chains = Vec::new();
624+
let mut seen_ancestors: SmallMap<Class, ClassType> = SmallMap::new();
624625
for (base, mro) in bases_with_mro {
625626
match &*mro {
626627
ClassMro::Resolved(ancestors) => {
@@ -629,6 +630,44 @@ impl Linearization {
629630
.map(|ancestor| ancestor.substitute_with(&base.substitution()))
630631
.rev()
631632
.collect::<Vec<_>>();
633+
let mut check_conflicting_targs = |ctype: &ClassType| -> bool {
634+
if let Some(prev) = seen_ancestors.get(ctype.class_object())
635+
&& (prev.targs().len() != ctype.targs().len()
636+
|| prev
637+
.targs()
638+
.as_slice()
639+
.iter()
640+
.zip(ctype.targs().as_slice())
641+
.any(|(ta, tb)| ta != tb && !ta.is_any() && !tb.is_any()))
642+
{
643+
let ctx = ClassDisplayContext::new(&[cls, ctype.class_object()]);
644+
// TODO: Extend this error message to say where in the class bases the mismatch comes from
645+
// we will need to wire additional information for this.
646+
errors.add(
647+
cls.range(),
648+
ErrorInfo::Kind(ErrorKind::InvalidInheritance),
649+
vec1![format!(
650+
"Class `{}` has inconsistent type arguments for base class `{}`: `{}` and `{}`",
651+
ctx.display(cls),
652+
ctx.display(ctype.class_object()),
653+
Type::ClassType(prev.clone()),
654+
Type::ClassType(ctype.clone()),
655+
)],
656+
);
657+
true
658+
} else {
659+
seen_ancestors.insert(ctype.class_object().dupe(), ctype.clone());
660+
false
661+
}
662+
};
663+
664+
for ancestor in ancestors_through_base.iter() {
665+
if check_conflicting_targs(ancestor) {
666+
break;
667+
}
668+
}
669+
check_conflicting_targs(base);
670+
632671
ancestor_chains.push(AncestorChain::from_base_and_ancestors(
633672
base.clone(),
634673
ancestors_through_base,

pyrefly/lib/test/generic_basic.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,6 @@ def g(t: type):
592592
);
593593

594594
testcase!(
595-
bug = "conformance: Should error on inconsistent type variable ordering in base classes",
596595
test_inconsistent_type_var_ordering_in_bases,
597596
r#"
598597
from typing import Generic, TypeVar
@@ -602,7 +601,28 @@ T2 = TypeVar("T2")
602601
603602
class Grandparent(Generic[T1, T2]): ...
604603
class Parent(Grandparent[T1, T2]): ...
605-
class BadChild(Parent[T1, T2], Grandparent[T2, T1]): ... # should be an error
604+
class BadChild(Parent[T1, T2], Grandparent[T2, T1]): ... # E: Class `BadChild` has inconsistent type arguments for base class `Grandparent`: `Grandparent[T1, T2]` and `Grandparent[T2, T1]`
605+
"#,
606+
);
607+
608+
testcase!(
609+
test_indirect_diamond_inconsistent_targs,
610+
r#"
611+
from typing import Generic, TypeVar
612+
613+
T = TypeVar("T")
614+
T1 = TypeVar("T1")
615+
T2 = TypeVar("T2")
616+
617+
class A(Generic[T]): ...
618+
class B(A[int]): ...
619+
class C(A[str]): ...
620+
class D(B, C): ... # E: Class `D` has inconsistent type arguments for base class `A`: `A[int]` and `A[str]`
621+
622+
class F(Generic[T1, T2]): ...
623+
class G(F[int, str]): ...
624+
class H(F[str, int]): ...
625+
class I(G, H): ... # E: Class `I` has inconsistent type arguments for base class `F`: `F[int, str]` and `F[str, int]`
606626
"#,
607627
);
608628

pyrefly/lib/test/tuple.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ class Base4(tuple[str, int]): ...
8888
8989
class Child1(Base1, Base2): ...
9090
class Child2(Base1, Base3): ...
91-
class Child3(Base3, Base4): ... # E: Cannot extend multiple incompatible tuples
92-
class Child4(Base2, Base3): ... # E: Cannot extend multiple incompatible tuples
91+
class Child3(Base3, Base4): ... # E: Class `Child3` has inconsistent type arguments for base class `Container`
92+
class Child4(Base2, Base3): ...
9393
"#,
9494
);
9595

0 commit comments

Comments
 (0)