From c60e8a037a739db141cb8af096c0b6d68b42b6ad Mon Sep 17 00:00:00 2001 From: David Peter Date: Mon, 10 Mar 2025 13:24:13 +0100 Subject: [PATCH] =?UTF-8?q?[red-knot]=20Add=20support=20for=20calling=20`t?= =?UTF-8?q?ype[=E2=80=A6]`=20(#16597)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This fixes the non-diagnostics part of #15948. ## Test Plan New Markdown tests. Negative diff on the ecosystem checks: ```diff zipp (https://github.com/jaraco/zipp) - error: lint:call-non-callable - --> /tmp/mypy_primer/projects/zipp/zipp/__init__.py:393:16 - | - 392 | def _next(self, at): - 393 | return self.__class__(self.root, at) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Object of type `type[Unknown]` is not callable - 394 | - 395 | def is_dir(self): - | - - Found 9 diagnostics + Found 8 diagnostics arrow (https://github.com/arrow-py/arrow) + | + | + warning: lint:unused-ignore-comment + --> /tmp/mypy_primer/projects/arrow/arrow/arrow.py:576:66 + 574 | values.append(1) + 575 | + 576 | floor = self.__class__(*values, tzinfo=self.tzinfo) # type: ignore[misc] + | -------------------- Unused blanket `type: ignore` directive + 577 | + 578 | if frame_absolute == "week": - error: lint:call-non-callable - --> /tmp/mypy_primer/projects/arrow/arrow/arrow.py:1080:16 - | - 1078 | dt = self._datetime.astimezone(tz) - 1079 | - 1080 | return self.__class__( - | ________________^ - 1081 | | dt.year, - 1082 | | dt.month, - 1083 | | dt.day, - 1084 | | dt.hour, - 1085 | | dt.minute, - 1086 | | dt.second, - 1087 | | dt.microsecond, - 1088 | | dt.tzinfo, - 1089 | | fold=getattr(dt, "fold", 0), - 1090 | | ) - | |_________^ Object of type `type[Unknown]` is not callable - 1091 | - 1092 | # string output and formatting - | black (https://github.com/psf/black) - - | - | - error: lint:call-non-callable - --> /tmp/mypy_primer/projects/black/src/blib2to3/pgen2/grammar.py:135:15 - 133 | Copy the grammar. - 134 | """ - 135 | new = self.__class__() - | ^^^^^^^^^^^^^^^^ Object of type `type[@Todo]` is not callable - 136 | for dict_attr in ( - 137 | "symbol2number", - Found 328 diagnostics + Found 327 diagnostics ``` --- .../resources/mdtest/call/subclass_of.md | 50 +++++++++++++++++++ crates/red_knot_python_semantic/src/types.rs | 7 +++ 2 files changed, 57 insertions(+) create mode 100644 crates/red_knot_python_semantic/resources/mdtest/call/subclass_of.md diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/subclass_of.md b/crates/red_knot_python_semantic/resources/mdtest/call/subclass_of.md new file mode 100644 index 0000000000..37082cfaa4 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/call/subclass_of.md @@ -0,0 +1,50 @@ +# Call `type[...]` + +## Single class + +### Trivial constructor + +```py +class C: ... + +def _(subclass_of_c: type[C]): + reveal_type(subclass_of_c()) # revealed: C +``` + +### Non-trivial constructor + +```py +class C: + def __init__(self, x: int): ... + +def _(subclass_of_c: type[C]): + reveal_type(subclass_of_c(1)) # revealed: C + + # TODO: Those should all be errors + reveal_type(subclass_of_c("a")) # revealed: C + reveal_type(subclass_of_c()) # revealed: C + reveal_type(subclass_of_c(1, 2)) # revealed: C +``` + +## Dynamic base + +```py +from typing import Any +from knot_extensions import Unknown + +def _(subclass_of_any: type[Any], subclass_of_unknown: type[Unknown]): + reveal_type(subclass_of_any()) # revealed: Any + reveal_type(subclass_of_any("any", "args", 1, 2)) # revealed: Any + reveal_type(subclass_of_unknown()) # revealed: Unknown + reveal_type(subclass_of_unknown("any", "args", 1, 2)) # revealed: Unknown +``` + +## Unions of classes + +```py +class A: ... +class B: ... + +def _(subclass_of_ab: type[A | B]): + reveal_type(subclass_of_ab()) # revealed: A | B +``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index a6b1520e71..9041af59e8 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2678,6 +2678,13 @@ impl<'db> Type<'db> { ))) } + Type::SubclassOf(subclass_of_type) => match subclass_of_type.subclass_of() { + ClassBase::Dynamic(dynamic_type) => Ok(CallOutcome::Single( + CallBinding::from_return_type(Type::Dynamic(dynamic_type)), + )), + ClassBase::Class(class) => Type::class_literal(class).try_call(db, arguments), + }, + instance_ty @ Type::Instance(_) => { instance_ty .try_call_dunder(db, "__call__", arguments)