diff --git a/misc/proper_plugin.py b/misc/proper_plugin.py index ed5fad36121e..4db3542705cd 100644 --- a/misc/proper_plugin.py +++ b/misc/proper_plugin.py @@ -50,10 +50,8 @@ def isinstance_proper_hook(ctx: FunctionContext) -> Type: right = get_proper_type(ctx.arg_types[1][0]) for arg in ctx.arg_types[0]: if ( - is_improper_type(arg) - or isinstance(get_proper_type(arg), AnyType) - and is_dangerous_target(right) - ): + is_improper_type(arg) or isinstance(get_proper_type(arg), AnyType) + ) and is_dangerous_target(right): if is_special_target(right): return ctx.default_return_type ctx.api.fail( diff --git a/mypy/checker.py b/mypy/checker.py index aed5017148ce..be07ad69d681 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2285,18 +2285,29 @@ def check_multiple_inheritance(self, typ: TypeInfo) -> None: if name in base2.names and base2 not in base.mro: self.check_compatibility(name, base, base2, typ) - def determine_type_of_class_member(self, sym: SymbolTableNode) -> Type | None: + def determine_type_of_member(self, sym: SymbolTableNode) -> Type | None: if sym.type is not None: return sym.type if isinstance(sym.node, FuncBase): return self.function_type(sym.node) if isinstance(sym.node, TypeInfo): - # nested class - return type_object_type(sym.node, self.named_type) + if sym.node.typeddict_type: + # We special-case TypedDict, because they don't define any constructor. + return self.expr_checker.typeddict_callable(sym.node) + else: + return type_object_type(sym.node, self.named_type) if isinstance(sym.node, TypeVarExpr): # Use of TypeVars is rejected in an expression/runtime context, so # we don't need to check supertype compatibility for them. return AnyType(TypeOfAny.special_form) + if isinstance(sym.node, TypeAlias): + with self.msg.filter_errors(): + # Suppress any errors, they will be given when analyzing the corresponding node. + # Here we may have incorrect options and location context. + return self.expr_checker.alias_type_in_runtime_context( + sym.node, sym.node.no_args, sym.node + ) + # TODO: handle more node kinds here. return None def check_compatibility( @@ -2327,8 +2338,8 @@ class C(B, A[int]): ... # this is unsafe because... return first = base1.names[name] second = base2.names[name] - first_type = get_proper_type(self.determine_type_of_class_member(first)) - second_type = get_proper_type(self.determine_type_of_class_member(second)) + first_type = get_proper_type(self.determine_type_of_member(first)) + second_type = get_proper_type(self.determine_type_of_member(second)) if isinstance(first_type, FunctionLike) and isinstance(second_type, FunctionLike): if first_type.is_type_obj() and second_type.is_type_obj(): diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 9bc65e9d7596..c72265207414 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -127,6 +127,7 @@ CallableType, DeletedType, ErasedType, + ExtraAttrs, FunctionLike, Instance, LiteralType, @@ -332,13 +333,7 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: result = erasetype.erase_typevars(result) elif isinstance(node, MypyFile): # Reference to a module object. - try: - result = self.named_type("types.ModuleType") - except KeyError: - # In test cases might 'types' may not be available. - # Fall back to a dummy 'object' type instead to - # avoid a crash. - result = self.named_type("builtins.object") + result = self.module_type(node) elif isinstance(node, Decorator): result = self.analyze_var_ref(node.var, e) elif isinstance(node, TypeAlias): @@ -374,6 +369,29 @@ def analyze_var_ref(self, var: Var, context: Context) -> Type: # Implicit 'Any' type. return AnyType(TypeOfAny.special_form) + def module_type(self, node: MypyFile) -> Instance: + try: + result = self.named_type("types.ModuleType") + except KeyError: + # In test cases might 'types' may not be available. + # Fall back to a dummy 'object' type instead to + # avoid a crash. + result = self.named_type("builtins.object") + module_attrs = {} + immutable = set() + for name, n in node.names.items(): + if isinstance(n.node, Var) and n.node.is_final: + immutable.add(name) + typ = self.chk.determine_type_of_member(n) + if typ: + module_attrs[name] = typ + else: + # TODO: what to do about nested module references? + # They are non-trivial because there may be import cycles. + module_attrs[name] = AnyType(TypeOfAny.special_form) + result.extra_attrs = ExtraAttrs(module_attrs, immutable, node.fullname) + return result + def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type: """Type check a call expression.""" if e.analyzed: diff --git a/mypy/checkmember.py b/mypy/checkmember.py index a025d1e04c86..ea2544442531 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -475,6 +475,9 @@ def analyze_member_var_access( return analyze_var(name, v, itype, info, mx, implicit=implicit) elif isinstance(v, FuncDef): assert False, "Did not expect a function" + elif isinstance(v, MypyFile): + mx.chk.module_refs.add(v.fullname) + return mx.chk.expr_checker.module_type(v) elif ( not v and name not in ["__getattr__", "__setattr__", "__getattribute__"] diff --git a/mypy/constraints.py b/mypy/constraints.py index e0742a33e9e8..50261824d92b 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -791,7 +791,7 @@ def infer_constraints_from_protocol_members( # The above is safe since at this point we know that 'instance' is a subtype # of (erased) 'template', therefore it defines all protocol members res.extend(infer_constraints(temp, inst, self.direction)) - if mypy.subtypes.IS_SETTABLE in mypy.subtypes.get_member_flags(member, protocol.type): + if mypy.subtypes.IS_SETTABLE in mypy.subtypes.get_member_flags(member, protocol): # Settable members are invariant, add opposite constraints res.extend(infer_constraints(temp, inst, neg_op(self.direction))) return res diff --git a/mypy/messages.py b/mypy/messages.py index 29fd1503e595..fa9b4e398394 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1822,6 +1822,7 @@ def report_protocol_problems( return class_obj = False + is_module = False if isinstance(subtype, TupleType): if not isinstance(subtype.partial_fallback, Instance): return @@ -1845,6 +1846,8 @@ def report_protocol_problems( return class_obj = True subtype = ret_type + if subtype.extra_attrs and subtype.extra_attrs.mod_name: + is_module = True # Report missing members missing = get_missing_protocol_members(subtype, supertype) @@ -1881,11 +1884,8 @@ def report_protocol_problems( or not subtype.type.defn.type_vars or not supertype.type.defn.type_vars ): - self.note( - f"Following member(s) of {format_type(subtype)} have conflicts:", - context, - code=code, - ) + type_name = format_type(subtype, module_names=True) + self.note(f"Following member(s) of {type_name} have conflicts:", context, code=code) for name, got, exp in conflict_types[:MAX_ITEMS]: exp = get_proper_type(exp) got = get_proper_type(got) @@ -1902,7 +1902,7 @@ def report_protocol_problems( self.note("Expected:", context, offset=OFFSET, code=code) if isinstance(exp, CallableType): self.note( - pretty_callable(exp, skip_self=class_obj), + pretty_callable(exp, skip_self=class_obj or is_module), context, offset=2 * OFFSET, code=code, @@ -1910,12 +1910,12 @@ def report_protocol_problems( else: assert isinstance(exp, Overloaded) self.pretty_overload( - exp, context, 2 * OFFSET, code=code, skip_self=class_obj + exp, context, 2 * OFFSET, code=code, skip_self=class_obj or is_module ) self.note("Got:", context, offset=OFFSET, code=code) if isinstance(got, CallableType): self.note( - pretty_callable(got, skip_self=class_obj), + pretty_callable(got, skip_self=class_obj or is_module), context, offset=2 * OFFSET, code=code, @@ -1923,7 +1923,7 @@ def report_protocol_problems( else: assert isinstance(got, Overloaded) self.pretty_overload( - got, context, 2 * OFFSET, code=code, skip_self=class_obj + got, context, 2 * OFFSET, code=code, skip_self=class_obj or is_module ) self.print_more(conflict_types, context, OFFSET, MAX_ITEMS, code=code) @@ -2147,7 +2147,9 @@ def format_callable_args( return ", ".join(arg_strings) -def format_type_inner(typ: Type, verbosity: int, fullnames: set[str] | None) -> str: +def format_type_inner( + typ: Type, verbosity: int, fullnames: set[str] | None, module_names: bool = False +) -> str: """ Convert a type to a relatively short string suitable for error messages. @@ -2187,7 +2189,10 @@ def format_literal_value(typ: LiteralType) -> str: # Get the short name of the type. if itype.type.fullname in ("types.ModuleType", "_importlib_modulespec.ModuleType"): # Make some common error messages simpler and tidier. - return "Module" + base_str = "Module" + if itype.extra_attrs and itype.extra_attrs.mod_name and module_names: + return f"{base_str} {itype.extra_attrs.mod_name}" + return base_str if verbosity >= 2 or (fullnames and itype.type.fullname in fullnames): base_str = itype.type.fullname else: @@ -2361,7 +2366,7 @@ def find_type_overlaps(*types: Type) -> set[str]: return overlaps -def format_type(typ: Type, verbosity: int = 0) -> str: +def format_type(typ: Type, verbosity: int = 0, module_names: bool = False) -> str: """ Convert a type to a relatively short string suitable for error messages. @@ -2372,10 +2377,10 @@ def format_type(typ: Type, verbosity: int = 0) -> str: modification of the formatted string is required, callers should use format_type_bare. """ - return quote_type_string(format_type_bare(typ, verbosity)) + return quote_type_string(format_type_bare(typ, verbosity, module_names)) -def format_type_bare(typ: Type, verbosity: int = 0) -> str: +def format_type_bare(typ: Type, verbosity: int = 0, module_names: bool = False) -> str: """ Convert a type to a relatively short string suitable for error messages. @@ -2387,7 +2392,7 @@ def format_type_bare(typ: Type, verbosity: int = 0) -> str: instead. (The caller may want to use quote_type_string after processing has happened, to maintain consistent quoting in messages.) """ - return format_type_inner(typ, verbosity, find_type_overlaps(typ)) + return format_type_inner(typ, verbosity, find_type_overlaps(typ), module_names) def format_type_distinctly(*types: Type, bare: bool = False) -> tuple[str, ...]: @@ -2564,7 +2569,7 @@ def get_conflict_protocol_types( if not subtype: continue is_compat = is_subtype(subtype, supertype, ignore_pos_arg_names=True) - if IS_SETTABLE in get_member_flags(member, right.type): + if IS_SETTABLE in get_member_flags(member, right): is_compat = is_compat and is_subtype(supertype, subtype) if not is_compat: conflicts.append((member, subtype, supertype)) @@ -2581,11 +2586,7 @@ def get_bad_protocol_flags( all_flags: list[tuple[str, set[int], set[int]]] = [] for member in right.type.protocol_members: if find_member(member, left, left): - item = ( - member, - get_member_flags(member, left.type), - get_member_flags(member, right.type), - ) + item = (member, get_member_flags(member, left), get_member_flags(member, right)) all_flags.append(item) bad_flags = [] for name, subflags, superflags in all_flags: diff --git a/mypy/server/deps.py b/mypy/server/deps.py index 121386c4c73d..45d7947641da 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -969,6 +969,9 @@ def visit_instance(self, typ: Instance) -> list[str]: triggers.extend(self.get_type_triggers(arg)) if typ.last_known_value: triggers.extend(self.get_type_triggers(typ.last_known_value)) + if typ.extra_attrs and typ.extra_attrs.mod_name: + # Module as type effectively depends on all module attributes, use wildcard. + triggers.append(make_wildcard_trigger(typ.extra_attrs.mod_name)) return triggers def visit_type_alias_type(self, typ: TypeAliasType) -> list[str]: diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 3aefa315db9e..1efdc7985e57 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1010,8 +1010,8 @@ def named_type(fullname: str) -> Instance: if isinstance(subtype, NoneType) and isinstance(supertype, CallableType): # We want __hash__ = None idiom to work even without --strict-optional return False - subflags = get_member_flags(member, left.type, class_obj=class_obj) - superflags = get_member_flags(member, right.type) + subflags = get_member_flags(member, left, class_obj=class_obj) + superflags = get_member_flags(member, right) if IS_SETTABLE in superflags: # Check opposite direction for settable attributes. if not is_subtype(supertype, subtype): @@ -1095,10 +1095,12 @@ def find_member( # PEP 544 doesn't specify anything about such use cases. So we just try # to do something meaningful (at least we should not crash). return TypeType(fill_typevars_with_any(v)) + if itype.extra_attrs and name in itype.extra_attrs.attrs: + return itype.extra_attrs.attrs[name] return None -def get_member_flags(name: str, info: TypeInfo, class_obj: bool = False) -> set[int]: +def get_member_flags(name: str, itype: Instance, class_obj: bool = False) -> set[int]: """Detect whether a member 'name' is settable, whether it is an instance or class variable, and whether it is class or static method. @@ -1109,6 +1111,7 @@ def get_member_flags(name: str, info: TypeInfo, class_obj: bool = False) -> set[ * IS_CLASS_OR_STATIC: set for methods decorated with @classmethod or with @staticmethod. """ + info = itype.type method = info.get_method(name) setattr_meth = info.get_method("__setattr__") if method: @@ -1126,11 +1129,18 @@ def get_member_flags(name: str, info: TypeInfo, class_obj: bool = False) -> set[ if not node: if setattr_meth: return {IS_SETTABLE} + if itype.extra_attrs and name in itype.extra_attrs.attrs: + flags = set() + if name not in itype.extra_attrs.immutable: + flags.add(IS_SETTABLE) + return flags return set() v = node.node # just a variable if isinstance(v, Var) and not v.is_property: - flags = {IS_SETTABLE} + flags = set() + if not v.is_final: + flags.add(IS_SETTABLE) if v.is_classvar: flags.add(IS_CLASSVAR) if class_obj and v.is_inferred: diff --git a/mypy/types.py b/mypy/types.py index e642858797ca..9fb0ede51a68 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1155,6 +1155,34 @@ def deserialize(cls, data: JsonDict) -> DeletedType: NOT_READY: Final = mypy.nodes.FakeInfo("De-serialization failure: TypeInfo not fixed") +class ExtraAttrs: + """Summary of module attributes and types. + + This is used for instances of types.ModuleType, because they can have different + attributes per instance. + """ + + def __init__( + self, + attrs: dict[str, Type], + immutable: set[str] | None = None, + mod_name: str | None = None, + ) -> None: + self.attrs = attrs + if immutable is None: + immutable = set() + self.immutable = immutable + self.mod_name = mod_name + + def __hash__(self) -> int: + return hash((tuple(self.attrs.items()), tuple(sorted(self.immutable)))) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, ExtraAttrs): + return NotImplemented + return self.attrs == other.attrs and self.immutable == other.immutable + + class Instance(ProperType): """An instance type of form C[T1, ..., Tn]. @@ -1186,7 +1214,7 @@ def try_getting_instance_fallback(typ: ProperType) -> Optional[Instance]: """ - __slots__ = ("type", "args", "invalid", "type_ref", "last_known_value", "_hash") + __slots__ = ("type", "args", "invalid", "type_ref", "last_known_value", "_hash", "extra_attrs") def __init__( self, @@ -1253,12 +1281,17 @@ def __init__( # Cached hash value self._hash = -1 + # Additional attributes defined per instance of this type. For example modules + # have different attributes per instance of types.ModuleType. This is intended + # to be "short lived", we don't serialize it, and even don't store as variable type. + self.extra_attrs: ExtraAttrs | None = None + def accept(self, visitor: TypeVisitor[T]) -> T: return visitor.visit_instance(self) def __hash__(self) -> int: if self._hash == -1: - self._hash = hash((self.type, self.args, self.last_known_value)) + self._hash = hash((self.type, self.args, self.last_known_value, self.extra_attrs)) return self._hash def __eq__(self, other: object) -> bool: @@ -1268,6 +1301,7 @@ def __eq__(self, other: object) -> bool: self.type == other.type and self.args == other.args and self.last_known_value == other.last_known_value + and self.extra_attrs == other.extra_attrs ) def serialize(self) -> JsonDict | str: @@ -1315,6 +1349,7 @@ def copy_modified( if last_known_value is not _dummy else self.last_known_value, ) + # We intentionally don't copy the extra_attrs here, so they will be erased. new.can_be_true = self.can_be_true new.can_be_false = self.can_be_false return new diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index bf011140df86..e4ab52e860a2 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -6022,3 +6022,39 @@ if foo: [out] [out2] main:4: error: Function "Callable[[], int]" could always be true in boolean context + +[case testModuleAsProtocolImplementationSerialize] +import m +[file m.py] +from typing import Protocol +from lib import C + +class Options(Protocol): + timeout: int + def update(self) -> bool: ... + +def setup(options: Options) -> None: ... +setup(C().config) + +[file lib.py] +import default_config + +class C: + config = default_config + +[file default_config.py] +timeout = 100 +def update() -> bool: ... + +[file default_config.py.2] +timeout = 100 +def update() -> str: ... +[builtins fixtures/module.pyi] +[out] +[out2] +tmp/m.py:9: error: Argument 1 to "setup" has incompatible type Module; expected "Options" +tmp/m.py:9: note: Following member(s) of "Module default_config" have conflicts: +tmp/m.py:9: note: Expected: +tmp/m.py:9: note: def update() -> bool +tmp/m.py:9: note: Got: +tmp/m.py:9: note: def update() -> str diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 520c9ae7d645..8d8598bc358e 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -1190,6 +1190,25 @@ z4 = y4 # E: Incompatible types in assignment (expression has type "PP", variabl # N: Protocol member PPS.attr expected settable variable, got read-only attribute [builtins fixtures/property.pyi] +[case testFinalAttributeProtocol] +from typing import Protocol, Final + +class P(Protocol): + x: int + +class C: + def __init__(self, x: int) -> None: + self.x = x +class CF: + def __init__(self, x: int) -> None: + self.x: Final = x + +x: P +y: P +x = C(42) +y = CF(42) # E: Incompatible types in assignment (expression has type "CF", variable has type "P") \ + # N: Protocol member P.x expected settable variable, got read-only attribute + [case testStaticAndClassMethodsInProtocols] from typing import Protocol, Type, TypeVar @@ -3577,3 +3596,173 @@ def f_good(t: S) -> S: g: C = f_bad # E: Incompatible types in assignment (expression has type "Callable[[int], int]", variable has type "C") \ # N: "C.__call__" has type "Callable[[Arg(T, 't')], T]" g = f_good # OK + +[case testModuleAsProtocolImplementation] +import default_config +import bad_config_1 +import bad_config_2 +import bad_config_3 +from typing import Protocol + +class Options(Protocol): + timeout: int + one_flag: bool + other_flag: bool + def update(self) -> bool: ... + +def setup(options: Options) -> None: ... +setup(default_config) # OK +setup(bad_config_1) # E: Argument 1 to "setup" has incompatible type Module; expected "Options" \ + # N: "ModuleType" is missing following "Options" protocol member: \ + # N: timeout +setup(bad_config_2) # E: Argument 1 to "setup" has incompatible type Module; expected "Options" \ + # N: Following member(s) of "Module bad_config_2" have conflicts: \ + # N: one_flag: expected "bool", got "int" +setup(bad_config_3) # E: Argument 1 to "setup" has incompatible type Module; expected "Options" \ + # N: Following member(s) of "Module bad_config_3" have conflicts: \ + # N: Expected: \ + # N: def update() -> bool \ + # N: Got: \ + # N: def update(obj: Any) -> bool + +[file default_config.py] +timeout = 100 +one_flag = True +other_flag = False +def update() -> bool: ... + +[file bad_config_1.py] +one_flag = True +other_flag = False +def update() -> bool: ... + +[file bad_config_2.py] +timeout = 100 +one_flag = 42 +other_flag = False +def update() -> bool: ... + +[file bad_config_3.py] +timeout = 100 +one_flag = True +other_flag = False +def update(obj) -> bool: ... +[builtins fixtures/module.pyi] + +[case testModuleAsProtocolImplementationInference] +import default_config +from typing import Protocol, TypeVar + +T = TypeVar("T", covariant=True) +class Options(Protocol[T]): + timeout: int + one_flag: bool + other_flag: bool + def update(self) -> T: ... + +def setup(options: Options[T]) -> T: ... +reveal_type(setup(default_config)) # N: Revealed type is "builtins.str" + +[file default_config.py] +timeout = 100 +one_flag = True +other_flag = False +def update() -> str: ... +[builtins fixtures/module.pyi] + +[case testModuleAsProtocolImplementationClassObject] +import runner +import bad_runner +from typing import Callable, Protocol + +class Runner(Protocol): + @property + def Run(self) -> Callable[[int], Result]: ... + +class Result(Protocol): + value: int + +def run(x: Runner) -> None: ... +run(runner) # OK +run(bad_runner) # E: Argument 1 to "run" has incompatible type Module; expected "Runner" \ + # N: Following member(s) of "Module bad_runner" have conflicts: \ + # N: Expected: \ + # N: def (int, /) -> Result \ + # N: Got: \ + # N: def __init__(arg: str) -> Run + +[file runner.py] +class Run: + value: int + def __init__(self, arg: int) -> None: ... + +[file bad_runner.py] +class Run: + value: int + def __init__(self, arg: str) -> None: ... +[builtins fixtures/module.pyi] + +[case testModuleAsProtocolImplementationTypeAlias] +import runner +import bad_runner +from typing import Callable, Protocol + +class Runner(Protocol): + @property + def run(self) -> Callable[[int], Result]: ... + +class Result(Protocol): + value: int + +def run(x: Runner) -> None: ... +run(runner) # OK +run(bad_runner) # E: Argument 1 to "run" has incompatible type Module; expected "Runner" \ + # N: Following member(s) of "Module bad_runner" have conflicts: \ + # N: Expected: \ + # N: def (int, /) -> Result \ + # N: Got: \ + # N: def __init__(arg: str) -> Run + +[file runner.py] +class Run: + value: int + def __init__(self, arg: int) -> None: ... +run = Run + +[file bad_runner.py] +class Run: + value: int + def __init__(self, arg: str) -> None: ... +run = Run +[builtins fixtures/module.pyi] + +[case testModuleAsProtocolImplementationClassVar] +from typing import ClassVar, Protocol +import mod + +class My(Protocol): + x: ClassVar[int] + +def test(mod: My) -> None: ... +test(mod=mod) # E: Argument "mod" to "test" has incompatible type Module; expected "My" \ + # N: Protocol member My.x expected class variable, got instance variable +[file mod.py] +x: int +[builtins fixtures/module.pyi] + +[case testModuleAsProtocolImplementationFinal] +from typing import Protocol +import some_module + +class My(Protocol): + a: int + +def func(arg: My) -> None: ... +func(some_module) # E: Argument 1 to "func" has incompatible type Module; expected "My" \ + # N: Protocol member My.a expected settable variable, got read-only attribute + +[file some_module.py] +from typing_extensions import Final + +a: Final = 1 +[builtins fixtures/module.pyi] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 3a054e8fcfe5..0e443abc7237 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -9933,3 +9933,39 @@ foo(name='Jennifer', age=38) [out] == m.py:2: error: Argument "age" to "foo" has incompatible type "int"; expected "str" + +[case testModuleAsProtocolImplementationFine] +import m +[file m.py] +from typing import Protocol +from lib import C + +class Options(Protocol): + timeout: int + def update(self) -> bool: ... + +def setup(options: Options) -> None: ... +setup(C().config) + +[file lib.py] +import default_config + +class C: + config = default_config + +[file default_config.py] +timeout = 100 +def update() -> bool: ... + +[file default_config.py.2] +timeout = 100 +def update() -> str: ... +[builtins fixtures/module.pyi] +[out] +== +m.py:9: error: Argument 1 to "setup" has incompatible type Module; expected "Options" +m.py:9: note: Following member(s) of "Module default_config" have conflicts: +m.py:9: note: Expected: +m.py:9: note: def update() -> bool +m.py:9: note: Got: +m.py:9: note: def update() -> str diff --git a/test-data/unit/fixtures/module.pyi b/test-data/unit/fixtures/module.pyi index ac1d3688ed12..98e989e59440 100644 --- a/test-data/unit/fixtures/module.pyi +++ b/test-data/unit/fixtures/module.pyi @@ -19,3 +19,4 @@ class ellipsis: pass classmethod = object() staticmethod = object() +property = object()