diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/same_names.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/same_names.md index f55d7d106c..6e6dfac60d 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/same_names.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/same_names.md @@ -170,6 +170,36 @@ class Container(Generic[T]): ## Protocols +### Differing members + +`bad.py`: + +```py +from typing import Protocol, TypeVar + +T_co = TypeVar("T_co", covariant=True) + +class Iterator(Protocol[T_co]): + def __nexxt__(self) -> T_co: ... + +def bad() -> Iterator[str]: + raise NotImplementedError +``` + +`main.py`: + +```py +from typing import Iterator + +def f() -> Iterator[str]: + import bad + + # error: [invalid-return-type] "Return type does not match returned value: expected `typing.Iterator[str]`, found `bad.Iterator[str]" + return bad.bad() +``` + +### Same members but with different types + ```py from typing import Protocol import proto_a diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index a2043c9b28..d3b18131b3 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -22,8 +22,8 @@ use crate::types::tuple::TupleSpec; use crate::types::visitor::TypeVisitor; use crate::types::{ BoundTypeVarInstance, CallableType, IntersectionType, KnownBoundMethodType, KnownClass, - MaterializationKind, Protocol, StringLiteralType, SubclassOfInner, Type, UnionType, - WrapperDescriptorKind, visitor, + MaterializationKind, Protocol, ProtocolInstanceType, StringLiteralType, SubclassOfInner, Type, + UnionType, WrapperDescriptorKind, visitor, }; use ruff_db::parsed::parsed_module; @@ -128,6 +128,13 @@ impl<'db> super::visitor::TypeVisitor<'db> for AmbiguousClassCollector<'db> { Type::ClassLiteral(class) => self.record_class(db, class), Type::EnumLiteral(literal) => self.record_class(db, literal.enum_class(db)), Type::GenericAlias(alias) => self.record_class(db, alias.origin(db)), + // Visit the class (as if it were a nominal-instance type) + // rather than the protocol members, if it is a class-based protocol. + // (For the purposes of displaying the type, we'll use the class name.) + Type::ProtocolInstance(ProtocolInstanceType { + inner: Protocol::FromClass(class), + .. + }) => return self.visit_type(db, Type::from(class)), _ => {} }