Skip to content

Commit 9e2f02e

Browse files
committed
Canonicalize type as type[Any]
This diff canonicalised the builtin type (which is a class) as a `Type::Type` with an implicit `Any`. The typing spec (https://typing.python.org/en/latest/spec/special-types.html#type) requires that "Plain `type` without brackets, the root of Python’s metaclass hierarchy, is equivalent to `type[Any]`." Whereas assignments of `type` and `type[Any]` are allowed by Pyrefly before this diff, they are not considered as identical types by `assert_type` call. After the canonicalisation introduced by this diff, they are considered as identical. As a result, we are able to pass 3 more conformance test cases. Since `type` itself is a class, we need to add some special treatment in the type system on inheritance, so that it can be inherited and used as a metaclass, while being a `Type::Type`. Some of the treatments are similar to how `tuple` is handled.
1 parent cf29d5f commit 9e2f02e

File tree

8 files changed

+65
-60
lines changed

8 files changed

+65
-60
lines changed

conformance/third_party/conformance.exp

Lines changed: 17 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9865,41 +9865,19 @@
98659865
{
98669866
"code": -2,
98679867
"column": 16,
9868-
"concise_description": "assert_type(type, type[Any]) failed",
9869-
"description": "assert_type(type, type[Any]) failed",
9870-
"line": 84,
9868+
"concise_description": "assert_type(Any, tuple[type[Any], ...]) failed",
9869+
"description": "assert_type(Any, tuple[type[Any], ...]) failed",
9870+
"line": 98,
98719871
"name": "assert-type",
98729872
"severity": "error",
9873-
"stop_column": 30,
9874-
"stop_line": 84
9875-
},
9876-
{
9877-
"code": -2,
9878-
"column": 17,
9879-
"concise_description": "Object of class `type` has no attribute `unknown`",
9880-
"description": "Object of class `type` has no attribute `unknown`",
9881-
"line": 99,
9882-
"name": "missing-attribute",
9883-
"severity": "error",
9884-
"stop_column": 26,
9885-
"stop_line": 99
9886-
},
9887-
{
9888-
"code": -2,
9889-
"column": 17,
9890-
"concise_description": "Object of class `type` has no attribute `unknown`",
9891-
"description": "Object of class `type` has no attribute `unknown`",
9892-
"line": 100,
9893-
"name": "missing-attribute",
9894-
"severity": "error",
9895-
"stop_column": 26,
9896-
"stop_line": 100
9873+
"stop_column": 45,
9874+
"stop_line": 98
98979875
},
98989876
{
98999877
"code": -2,
99009878
"column": 16,
9901-
"concise_description": "assert_type(Any, tuple[type, ...]) failed",
9902-
"description": "assert_type(Any, tuple[type, ...]) failed",
9879+
"concise_description": "assert_type(Any, tuple[type[Any], ...]) failed",
9880+
"description": "assert_type(Any, tuple[type[Any], ...]) failed",
99039881
"line": 102,
99049882
"name": "assert-type",
99059883
"severity": "error",
@@ -9909,8 +9887,8 @@
99099887
{
99109888
"code": -2,
99119889
"column": 16,
9912-
"concise_description": "assert_type(Any, tuple[type, ...]) failed",
9913-
"description": "assert_type(Any, tuple[type, ...]) failed",
9890+
"concise_description": "assert_type(Any, tuple[type[Any], ...]) failed",
9891+
"description": "assert_type(Any, tuple[type[Any], ...]) failed",
99149892
"line": 106,
99159893
"name": "assert-type",
99169894
"severity": "error",
@@ -9920,8 +9898,8 @@
99209898
{
99219899
"code": -2,
99229900
"column": 16,
9923-
"concise_description": "assert_type(Any, tuple[type, ...]) failed",
9924-
"description": "assert_type(Any, tuple[type, ...]) failed",
9901+
"concise_description": "assert_type(Any, tuple[type[Any], ...]) failed",
9902+
"description": "assert_type(Any, tuple[type[Any], ...]) failed",
99259903
"line": 110,
99269904
"name": "assert-type",
99279905
"severity": "error",
@@ -9950,22 +9928,11 @@
99509928
"stop_column": 14,
99519929
"stop_line": 120
99529930
},
9953-
{
9954-
"code": -2,
9955-
"column": 16,
9956-
"concise_description": "assert_type(type, type[Any]) failed",
9957-
"description": "assert_type(type, type[Any]) failed",
9958-
"line": 139,
9959-
"name": "assert-type",
9960-
"severity": "error",
9961-
"stop_column": 30,
9962-
"stop_line": 139
9963-
},
99649931
{
99659932
"code": -2,
99669933
"column": 1,
9967-
"concise_description": "TODO: Expr::attr_infer_for_type attribute base undefined for type: TypeAlias[TA1, type[type[Unknown]]] (trying to access unknown)",
9968-
"description": "TODO: Expr::attr_infer_for_type attribute base undefined for type: TypeAlias[TA1, type[type[Unknown]]] (trying to access unknown)",
9934+
"concise_description": "TODO: Expr::attr_infer_for_type attribute base undefined for type: TypeAlias[TA1, type[type]] (trying to access unknown)",
9935+
"description": "TODO: Expr::attr_infer_for_type attribute base undefined for type: TypeAlias[TA1, type[type]] (trying to access unknown)",
99699936
"line": 143,
99709937
"name": "missing-attribute",
99719938
"severity": "error",
@@ -9986,8 +9953,8 @@
99869953
{
99879954
"code": -2,
99889955
"column": 1,
9989-
"concise_description": "Class `type` has no class attribute `unknown`",
9990-
"description": "Class `type` has no class attribute `unknown`",
9956+
"concise_description": "TODO: Expr::attr_infer_for_type attribute base undefined for type: TypeAlias[TA3, type[type]] (trying to access unknown)",
9957+
"description": "TODO: Expr::attr_infer_for_type attribute base undefined for type: TypeAlias[TA3, type[type]] (trying to access unknown)",
99919958
"line": 145,
99929959
"name": "missing-attribute",
99939960
"severity": "error",
@@ -10612,8 +10579,8 @@
1061210579
{
1061310580
"code": -2,
1061410581
"column": 7,
10615-
"concise_description": "Typed dictionary definitions may not specify a metaclass",
10616-
"description": "Typed dictionary definitions may not specify a metaclass",
10582+
"concise_description": "Metaclass of `BadTypedDict2` has type `type` that is not a simple class type",
10583+
"description": "Metaclass of `BadTypedDict2` has type `type` that is not a simple class type",
1061710584
"line": 44,
1061810585
"name": "invalid-inheritance",
1061910586
"severity": "error",

conformance/third_party/conformance.result

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -281,13 +281,10 @@
281281
"specialtypes_promotions.py": [],
282282
"specialtypes_type.py": [
283283
"Line 70: Expected 1 errors",
284-
"Line 84: Unexpected errors ['assert_type(type, type[Any]) failed']",
285-
"Line 99: Unexpected errors ['Object of class `type` has no attribute `unknown`']",
286-
"Line 100: Unexpected errors ['Object of class `type` has no attribute `unknown`']",
287-
"Line 102: Unexpected errors ['assert_type(Any, tuple[type, ...]) failed']",
288-
"Line 106: Unexpected errors ['assert_type(Any, tuple[type, ...]) failed']",
289-
"Line 110: Unexpected errors ['assert_type(Any, tuple[type, ...]) failed']",
290-
"Line 139: Unexpected errors ['assert_type(type, type[Any]) failed']"
284+
"Line 98: Unexpected errors ['assert_type(Any, tuple[type[Any], ...]) failed']",
285+
"Line 102: Unexpected errors ['assert_type(Any, tuple[type[Any], ...]) failed']",
286+
"Line 106: Unexpected errors ['assert_type(Any, tuple[type[Any], ...]) failed']",
287+
"Line 110: Unexpected errors ['assert_type(Any, tuple[type[Any], ...]) failed']"
291288
],
292289
"tuples_type_compat.py": [],
293290
"tuples_type_form.py": [],

conformance/third_party/results.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"pass": 106,
44
"fail": 32,
55
"pass_rate": 0.77,
6-
"differences": 136,
6+
"differences": 133,
77
"passing": [
88
"aliases_explicit.py",
99
"aliases_newtype.py",
@@ -144,7 +144,7 @@
144144
"qualifiers_annotated.py": 6,
145145
"qualifiers_final_annotation.py": 7,
146146
"specialtypes_never.py": 1,
147-
"specialtypes_type.py": 8
147+
"specialtypes_type.py": 5
148148
},
149149
"comment": "@generated"
150150
}

crates/pyrefly_types/src/display.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,7 @@ impl<'a> TypeDisplayContext<'a> {
708708
output.write_str(ta.name.as_str())
709709
}
710710
}
711+
Type::Type(box Type::Any(AnyStyle::Implicit)) => output.write_str("type"),
711712
Type::Type(ty) => {
712713
output.write_str("type[")?;
713714
self.fmt_helper_generic(ty, false, output)?;

pyrefly/lib/alt/class/class_metadata.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use pyrefly_types::type_var::Restriction;
1919
use pyrefly_types::typed_dict::ExtraItem;
2020
use pyrefly_types::typed_dict::ExtraItems;
2121
use pyrefly_types::typed_dict::TypedDict;
22+
use pyrefly_types::types::AnyStyle;
2223
use pyrefly_types::types::Forallable;
2324
use pyrefly_util::display::DisplayWithCtx;
2425
use pyrefly_util::prelude::SliceExt;
@@ -914,6 +915,18 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
914915
}
915916
}
916917
}
918+
Type::Type(box Type::Any(AnyStyle::Implicit)) => {
919+
// `type[Any]` is canoicalized from `type` or `Type`
920+
let type_obj = self.stdlib.builtins_type().class_object();
921+
let metadata = self.get_metadata_for_class(type_obj);
922+
BaseClassParseResult::Parsed({
923+
ParsedBaseClass {
924+
class_object: type_obj.dupe(),
925+
range,
926+
metadata,
927+
}
928+
})
929+
}
917930
_ => {
918931
if is_new_type || !ty.is_any() {
919932
BaseClassParseResult::InvalidType(ty, range)

pyrefly/lib/alt/expr.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,12 +1289,18 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
12891289
Type::ClassDef(cls) => {
12901290
if cls.is_builtin("tuple") {
12911291
*ty = Type::type_form(Type::unbounded_tuple(Type::Any(AnyStyle::Implicit)));
1292+
} else if cls.is_builtin("type") {
1293+
// type is equivalent to type[Any]
1294+
*ty = Type::type_form(Type::type_form(Type::Any(AnyStyle::Implicit)));
12921295
} else if cls.has_toplevel_qname("typing", "Any") {
12931296
*ty = Type::type_form(Type::any_explicit())
12941297
} else {
12951298
*ty = Type::type_form(self.promote(cls, range, errors));
12961299
}
12971300
}
1301+
Type::ClassType(cls) if cls.is_builtin("type") => {
1302+
*ty = Type::type_form(Type::Any(AnyStyle::Implicit));
1303+
}
12981304
_ => {}
12991305
})
13001306
}

pyrefly/lib/alt/types/class_bases.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use pyrefly_python::short_identifier::ShortIdentifier;
1515
use pyrefly_types::class::ClassType;
1616
use pyrefly_types::special_form::SpecialForm;
1717
use pyrefly_types::typed_dict::TypedDict;
18+
use pyrefly_types::types::AnyStyle;
1819
use pyrefly_util::display::commas_iter;
1920
use ruff_python_ast::Expr;
2021
use ruff_text_size::Ranged;
@@ -299,6 +300,13 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
299300
}
300301
}
301302
}
303+
(Type::Type(box Type::Any(AnyStyle::Implicit)), range) => {
304+
// `type` is canonicalized to `Type[Any]`, so we need to handle it here.
305+
let class = self.stdlib.builtins_type().clone();
306+
let bases = self
307+
.get_base_types_for_class(self.stdlib.builtins_type().class_object());
308+
Some((class, bases, range))
309+
}
302310
(_, _) => None,
303311
}
304312
})

pyrefly/lib/test/simple.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,19 @@ c2: type[C, C] = C # E: Expected 1 type argument for `type`, got 2
844844
"#,
845845
);
846846

847+
testcase!(
848+
test_type_without_argument_is_equivalent_to_type_any,
849+
r#"
850+
from typing import assert_type, Any
851+
def f(x: type) -> None:
852+
g(x)
853+
assert_type(x, type[Any])
854+
def g(x: type[Any]) -> None:
855+
f(x)
856+
assert_type(x, type)
857+
"#,
858+
);
859+
847860
testcase!(
848861
test_annotated,
849862
r#"
@@ -1402,7 +1415,7 @@ def g(x: type) -> None: ...
14021415
f(int)
14031416
f(Type)
14041417
f(type)
1405-
f(42) # E: not assignable to parameter `x` with type `type[Unknown]`
1418+
f(42) # E: not assignable to parameter `x` with type `type`
14061419
14071420
g(int)
14081421
g(Type)

0 commit comments

Comments
 (0)