diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 4ac9434382..7a212d442a 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -690,13 +690,11 @@ impl<'db> Type<'db> { Type::Instance(InstanceType { class: self_class }), Type::Instance(InstanceType { class: target_class }) ) - if ( - self_class.is_known(db, KnownClass::NoneType) && - target_class.is_known(db, KnownClass::NoneType) - ) || ( - self_class.is_known(db, KnownClass::NoDefaultType) && - target_class.is_known(db, KnownClass::NoDefaultType) - ) + if { + let self_known = self_class.known(db); + matches!(self_known, Some(KnownClass::NoneType | KnownClass::NoDefaultType)) + && self_known == target_class.known(db) + } ) } @@ -937,11 +935,7 @@ impl<'db> Type<'db> { | Type::ModuleLiteral(..) | Type::KnownInstance(..) => true, Type::Instance(InstanceType { class }) => { - if let Some(known_class) = class.known(db) { - known_class.is_singleton() - } else { - false - } + class.known(db).is_some_and(KnownClass::is_singleton) } Type::Tuple(..) => { // The empty tuple is a singleton on CPython and PyPy, but not on other Python @@ -1595,6 +1589,9 @@ impl<'db> KnownClass { Self::NoneType => typeshed_symbol(db, self.as_str()).unwrap_or_unknown(), Self::SpecialForm => typing_symbol(db, self.as_str()).unwrap_or_unknown(), Self::TypeVar => typing_symbol(db, self.as_str()).unwrap_or_unknown(), + // TODO when we understand sys.version_info, we will need an explicit fallback here, + // because typing_extensions has a 3.13+ re-export for the `typing.NoDefault` + // singleton, but not for `typing._NoDefaultType` Self::NoDefaultType => typing_extensions_symbol(db, self.as_str()).unwrap_or_unknown(), } } @@ -3324,6 +3321,20 @@ mod tests { assert_eq!(ty.into_type(&db).repr(&db), expected.into_type(&db)); } + #[test] + fn typing_vs_typeshed_no_default() { + let db = setup_db(); + + let typing_no_default = typing_symbol(&db, "NoDefault").expect_type(); + let typing_extensions_no_default = typing_extensions_symbol(&db, "NoDefault").expect_type(); + + assert_eq!(typing_no_default.display(&db).to_string(), "NoDefault"); + assert_eq!( + typing_extensions_no_default.display(&db).to_string(), + "NoDefault" + ); + } + #[test] fn module_type_symbols_includes_declared_types_but_not_referenced_types() { let db = setup_db(); diff --git a/crates/red_knot_python_semantic/src/types/display.rs b/crates/red_knot_python_semantic/src/types/display.rs index 76747bac67..20e5bc1ba3 100644 --- a/crates/red_knot_python_semantic/src/types/display.rs +++ b/crates/red_knot_python_semantic/src/types/display.rs @@ -66,15 +66,13 @@ impl Display for DisplayRepresentation<'_> { Type::Any => f.write_str("Any"), Type::Never => f.write_str("Never"), Type::Unknown => f.write_str("Unknown"), - Type::Instance(InstanceType { class }) - if class.is_known(self.db, KnownClass::NoneType) => - { - f.write_str("None") - } - Type::Instance(InstanceType { class }) - if class.is_known(self.db, KnownClass::NoDefaultType) => - { - f.write_str("NoDefault") + Type::Instance(InstanceType { class }) => { + let representation = match class.known(self.db) { + Some(KnownClass::NoneType) => "None", + Some(KnownClass::NoDefaultType) => "NoDefault", + _ => class.name(self.db), + }; + f.write_str(representation) } // `[Type::Todo]`'s display should be explicit that is not a valid display of // any other type @@ -87,7 +85,6 @@ impl Display for DisplayRepresentation<'_> { Type::SubclassOf(SubclassOfType { class }) => { write!(f, "type[{}]", class.name(self.db)) } - Type::Instance(InstanceType { class }) => f.write_str(class.name(self.db)), Type::KnownInstance(known_instance) => f.write_str(known_instance.as_str()), Type::FunctionLiteral(function) => f.write_str(function.name(self.db)), Type::Union(union) => union.display(self.db).fmt(f),