Skip to content

Commit c167e47

Browse files
committed
Simplifications
1 parent c799003 commit c167e47

File tree

7 files changed

+96
-94
lines changed

7 files changed

+96
-94
lines changed

crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class RepeatedTypevar(Generic[T, T]): ...
3737
You can only specialize `typing.Generic` with typevars (TODO: or param specs or typevar tuples).
3838

3939
```py
40-
# error: [invalid-argument-type] "`<class 'int'>` is not a valid argument to `typing.Generic`"
40+
# error: [invalid-argument-type] "`<class 'int'>` is not a valid argument to `Generic`"
4141
class GenericOfType(Generic[int]): ...
4242
```
4343

crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ T = TypeVar("T")
6767

6868
# error: [invalid-generic-class] "Cannot both inherit from `typing.Generic` and use PEP 695 type variables"
6969
class BothGenericSyntaxes[U](Generic[T]): ...
70+
reveal_type(BothGenericSyntaxes.__mro__) # revealed: tuple[<class 'BothGenericSyntaxes[Unknown]'>, Unknown, <class 'object'>]
71+
72+
# error: [invalid-generic-class] "Cannot both inherit from `typing.Generic` and use PEP 695 type variables"
73+
# error: [invalid-base] "Cannot inherit from plain `Generic`"
74+
class DoublyInvalid[T](Generic): ...
75+
reveal_type(DoublyInvalid.__mro__) # revealed: tuple[<class 'DoublyInvalid[Unknown]'>, Unknown, <class 'object'>]
7076
```
7177

7278
Generic classes implicitly inherit from `Generic`:

crates/ty_python_semantic/resources/mdtest/protocols.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,13 @@ class Bar1(Protocol[T], Generic[T]):
5858
class Bar2[T](Protocol):
5959
x: T
6060

61-
# error: [invalid-generic-class] "Cannot both inherit from subscripted `typing.Protocol` and use PEP 695 type variables"
61+
# error: [invalid-generic-class] "Cannot both inherit from subscripted `Protocol` and use PEP 695 type variables"
6262
class Bar3[T](Protocol[T]):
6363
x: T
64+
65+
# Note that this class definition *will* actually succeed at runtime,
66+
# unlike classes that combine PEP-695 type parameters with inheritance from `Generic[]`
67+
reveal_type(Bar3.__mro__) # revealed: tuple[<class 'Bar3[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
6468
```
6569

6670
It's an error to include both bare `Protocol` and subscripted `Protocol[]` in the bases list

crates/ty_python_semantic/src/types/class.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -580,10 +580,6 @@ impl<'db> ClassLiteral<'db> {
580580
self.pep695_generic_context(db).is_some()
581581
}
582582

583-
pub(crate) fn has_legacy_generic_context(self, db: &'db dyn Db) -> bool {
584-
self.legacy_generic_context(db).is_some()
585-
}
586-
587583
#[salsa::tracked(cycle_fn=pep695_generic_context_cycle_recover, cycle_initial=pep695_generic_context_cycle_initial)]
588584
pub(crate) fn pep695_generic_context(self, db: &'db dyn Db) -> Option<GenericContext<'db>> {
589585
let scope = self.body_scope(db);

crates/ty_python_semantic/src/types/generics.rs

Lines changed: 9 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ use crate::{Db, FxOrderSet};
2727
pub struct GenericContext<'db> {
2828
#[returns(ref)]
2929
pub(crate) variables: FxOrderSet<TypeVarInstance<'db>>,
30-
pub(crate) origin: GenericContextOrigin,
3130
}
3231

3332
impl<'db> GenericContext<'db> {
@@ -41,7 +40,7 @@ impl<'db> GenericContext<'db> {
4140
.iter()
4241
.filter_map(|type_param| Self::variable_from_type_param(db, index, type_param))
4342
.collect();
44-
Self::new(db, variables, GenericContextOrigin::TypeParameterList)
43+
Self::new(db, variables)
4544
}
4645

4746
fn variable_from_type_param(
@@ -87,11 +86,7 @@ impl<'db> GenericContext<'db> {
8786
if variables.is_empty() {
8887
return None;
8988
}
90-
Some(Self::new(
91-
db,
92-
variables,
93-
GenericContextOrigin::LegacyGenericFunction,
94-
))
89+
Some(Self::new(db, variables))
9590
}
9691

9792
/// Creates a generic context from the legacy `TypeVar`s that appear in class's base class
@@ -107,7 +102,7 @@ impl<'db> GenericContext<'db> {
107102
if variables.is_empty() {
108103
return None;
109104
}
110-
Some(Self::new(db, variables, GenericContextOrigin::Inherited))
105+
Some(Self::new(db, variables))
111106
}
112107

113108
pub(crate) fn len(self, db: &'db dyn Db) -> usize {
@@ -244,46 +239,21 @@ impl<'db> GenericContext<'db> {
244239
.iter()
245240
.map(|ty| ty.normalized(db))
246241
.collect();
247-
Self::new(db, variables, self.origin(db))
248-
}
249-
}
250-
251-
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
252-
pub enum GenericContextOrigin {
253-
LegacyBase(LegacyGenericBase),
254-
Inherited,
255-
LegacyGenericFunction,
256-
TypeParameterList,
257-
}
258-
259-
impl GenericContextOrigin {
260-
pub(crate) const fn as_str(self) -> &'static str {
261-
match self {
262-
Self::LegacyBase(base) => base.as_str(),
263-
Self::Inherited => "inherited",
264-
Self::LegacyGenericFunction => "legacy generic function",
265-
Self::TypeParameterList => "type parameter list",
266-
}
242+
Self::new(db, variables)
267243
}
268244
}
269245

270-
impl std::fmt::Display for GenericContextOrigin {
271-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
272-
f.write_str(self.as_str())
273-
}
274-
}
275-
276-
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
277-
pub enum LegacyGenericBase {
246+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
247+
pub(super) enum LegacyGenericBase {
278248
Generic,
279249
Protocol,
280250
}
281251

282252
impl LegacyGenericBase {
283-
pub(crate) const fn as_str(self) -> &'static str {
253+
const fn as_str(self) -> &'static str {
284254
match self {
285-
Self::Generic => "`typing.Generic`",
286-
Self::Protocol => "subscripted `typing.Protocol`",
255+
Self::Generic => "Generic",
256+
Self::Protocol => "Protocol",
287257
}
288258
}
289259
}
@@ -294,12 +264,6 @@ impl std::fmt::Display for LegacyGenericBase {
294264
}
295265
}
296266

297-
impl From<LegacyGenericBase> for GenericContextOrigin {
298-
fn from(base: LegacyGenericBase) -> Self {
299-
Self::LegacyBase(base)
300-
}
301-
}
302-
303267
/// An assignment of a specific type to each type variable in a generic scope.
304268
///
305269
/// TODO: Handle nested specializations better, with actual parent links to the specialization of

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ use super::diagnostic::{
108108
report_runtime_check_against_non_runtime_checkable_protocol, report_slice_step_size_zero,
109109
report_unresolved_reference,
110110
};
111-
use super::generics::{GenericContextOrigin, LegacyGenericBase};
111+
use super::generics::LegacyGenericBase;
112112
use super::slots::check_class_slots;
113113
use super::string_annotation::{
114114
BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION, parse_string_annotation,
@@ -856,6 +856,25 @@ impl<'db> TypeInferenceBuilder<'db> {
856856
}
857857
continue;
858858
}
859+
// Note that unlike several of the other errors caught in this function,
860+
// this does not lead to the class creation failing at runtime,
861+
// but it is semantically invalid.
862+
Type::KnownInstance(KnownInstanceType::Protocol(Some(_))) => {
863+
if class_node.type_params.is_none() {
864+
continue;
865+
}
866+
let Some(builder) = self
867+
.context
868+
.report_lint(&INVALID_GENERIC_CLASS, &class_node.bases()[i])
869+
else {
870+
continue;
871+
};
872+
builder.into_diagnostic(
873+
"Cannot both inherit from subscripted `Protocol` \
874+
and use PEP 695 type variables",
875+
);
876+
continue;
877+
}
859878
Type::ClassLiteral(class) => class,
860879
// dynamic/unknown bases are never `@final`
861880
_ => continue,
@@ -912,27 +931,28 @@ impl<'db> TypeInferenceBuilder<'db> {
912931
}
913932
}
914933
MroErrorKind::UnresolvableMro { bases_list } => {
915-
let has_conflicting_generic_contexts = class
916-
.has_pep_695_type_params(self.db())
917-
&& class.has_legacy_generic_context(self.db());
918-
919-
// Skip reporting the error if the class has conflicting generic contexts.
920-
// We'll report an error below for that with a nicer error message;
921-
// there's not much need to have two diagnostics for the same underlying problem.
922-
if !has_conflicting_generic_contexts {
923-
if let Some(builder) =
924-
self.context.report_lint(&INCONSISTENT_MRO, class_node)
925-
{
926-
builder.into_diagnostic(format_args!(
927-
"Cannot create a consistent method resolution order (MRO) \
928-
for class `{}` with bases list `[{}]`",
929-
class.name(self.db()),
930-
bases_list
931-
.iter()
932-
.map(|base| base.display(self.db()))
933-
.join(", ")
934-
));
935-
}
934+
if let Some(builder) =
935+
self.context.report_lint(&INCONSISTENT_MRO, class_node)
936+
{
937+
builder.into_diagnostic(format_args!(
938+
"Cannot create a consistent method resolution order (MRO) \
939+
for class `{}` with bases list `[{}]`",
940+
class.name(self.db()),
941+
bases_list
942+
.iter()
943+
.map(|base| base.display(self.db()))
944+
.join(", ")
945+
));
946+
}
947+
}
948+
MroErrorKind::Pep695ClassWithGenericInheritance => {
949+
if let Some(builder) =
950+
self.context.report_lint(&INVALID_GENERIC_CLASS, class_node)
951+
{
952+
builder.into_diagnostic(
953+
"Cannot both inherit from `typing.Generic` \
954+
and use PEP 695 type variables",
955+
);
936956
}
937957
}
938958
MroErrorKind::InheritanceCycle => {
@@ -1031,21 +1051,6 @@ impl<'db> TypeInferenceBuilder<'db> {
10311051
}
10321052
}
10331053

1034-
// (5) Check that a generic class does not have invalid or conflicting generic
1035-
// contexts.
1036-
if class.pep695_generic_context(self.db()).is_some() {
1037-
if let Some(legacy_context) = class.legacy_generic_context(self.db()) {
1038-
if let Some(builder) =
1039-
self.context.report_lint(&INVALID_GENERIC_CLASS, class_node)
1040-
{
1041-
builder.into_diagnostic(format_args!(
1042-
"Cannot both inherit from {} and use PEP 695 type variables",
1043-
legacy_context.origin(self.db())
1044-
));
1045-
}
1046-
}
1047-
}
1048-
10491054
if let (Some(legacy), Some(inherited)) = (
10501055
class.legacy_generic_context(self.db()),
10511056
class.inherited_legacy_generic_context(self.db()),
@@ -7631,17 +7636,15 @@ impl<'db> TypeInferenceBuilder<'db> {
76317636
self.context.report_lint(&INVALID_ARGUMENT_TYPE, value_node)
76327637
{
76337638
builder.into_diagnostic(format_args!(
7634-
"`{}` is not a valid argument to {origin}",
7639+
"`{}` is not a valid argument to `{origin}`",
76357640
typevar.display(self.db()),
76367641
));
76377642
}
76387643
None
76397644
}
76407645
})
76417646
.collect();
7642-
typevars.map(|typevars| {
7643-
GenericContext::new(self.db(), typevars, GenericContextOrigin::from(origin))
7644-
})
7647+
typevars.map(|typevars| GenericContext::new(self.db(), typevars))
76457648
}
76467649

76477650
fn infer_slice_expression(&mut self, slice: &ast::ExprSlice) -> Type<'db> {

crates/ty_python_semantic/src/types/mro.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,21 @@ impl<'db> Mro<'db> {
7272
) -> Result<Self, MroErrorKind<'db>> {
7373
/// Possibly add `Generic` to the resolved bases list.
7474
///
75-
/// This emulates the behavior of `typing._GenericAlias.__mro_entries__` at
75+
/// This function is called in two cases:
76+
/// - If we encounter a subscripted `Generic` in the original bases list
77+
/// (`Generic[T]` or similar)
78+
/// - If the class has PEP-695 type parameters,
79+
/// `Generic` is [implicitly appended] to the bases list at runtime
80+
///
81+
/// Whether or not `Generic` is added to the bases list depends on:
82+
/// - Whether `Protocol` is present in the original bases list
83+
/// - Whether any of the bases yet to be visited in the original bases list
84+
/// is a generic alias (which would therefore have `Generic` in its MRO)
85+
///
86+
/// This function emulates the behavior of `typing._GenericAlias.__mro_entries__` at
7687
/// <https://github.com/python/cpython/blob/ad42dc1909bdf8ec775b63fb22ed48ff42797a17/Lib/typing.py#L1487-L1500>.
88+
///
89+
/// [implicitly inherits]: https://docs.python.org/3/reference/compound_stmts.html#generic-classes
7790
fn maybe_add_generic<'db>(
7891
resolved_bases: &mut Vec<ClassBase<'db>>,
7992
original_bases: &[Type<'db>],
@@ -161,7 +174,7 @@ impl<'db> Mro<'db> {
161174
let mut invalid_bases = vec![];
162175

163176
for (i, base) in original_bases.iter().enumerate() {
164-
// Note that emit a diagnostic for inheriting from bare (unsubscripted) `Generic` elsewhere
177+
// Note that we emit a diagnostic for inheriting from bare (unsubscripted) `Generic` elsewhere
165178
// (see `infer::TypeInferenceBuilder::check_class_definitions`),
166179
// which is why we only care about `KnownInstanceType::Generic(Some(_))`,
167180
// not `KnownInstanceType::Generic(None)`.
@@ -184,6 +197,7 @@ impl<'db> Mro<'db> {
184197
}
185198

186199
// `Generic` is implicitly added to the bases list of a class that has PEP-695 type parameters
200+
// (documented at https://docs.python.org/3/reference/compound_stmts.html#generic-classes)
187201
if class.has_pep_695_type_params(db) {
188202
maybe_add_generic(&mut resolved_bases, original_bases, &[]);
189203
}
@@ -206,6 +220,18 @@ impl<'db> Mro<'db> {
206220
return Ok(mro);
207221
}
208222

223+
// We now know that the MRO is unresolvable through the C3-merge algorithm.
224+
// The rest of this function is dedicated to figuring out the best error message
225+
// to report to the user.
226+
227+
if class.has_pep_695_type_params(db)
228+
&& original_bases.iter().any(|base| {
229+
matches!(base, Type::KnownInstance(KnownInstanceType::Generic(_)))
230+
})
231+
{
232+
return Err(MroErrorKind::Pep695ClassWithGenericInheritance);
233+
}
234+
209235
let mut duplicate_dynamic_bases = false;
210236

211237
let duplicate_bases: Vec<DuplicateBaseError<'db>> = {
@@ -430,6 +456,9 @@ pub(super) enum MroErrorKind<'db> {
430456
/// See [`DuplicateBaseError`] for more details.
431457
DuplicateBases(Box<[DuplicateBaseError<'db>]>),
432458

459+
/// The class uses PEP-695 parameters and also inherits from `Generic[]`.
460+
Pep695ClassWithGenericInheritance,
461+
433462
/// A cycle was encountered resolving the class' bases.
434463
InheritanceCycle,
435464

0 commit comments

Comments
 (0)