From ed5ccd6b45059c2269f1b7e1e689ce086b45c929 Mon Sep 17 00:00:00 2001 From: InSyncWithFoo Date: Mon, 9 Dec 2024 05:11:06 +0000 Subject: [PATCH 1/6] [red-knot] Understanding `type[Union[A, B]]` --- .../resources/mdtest/type_of/basic.md | 20 ++++++++++++++++ .../src/types/infer.rs | 23 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md b/crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md index 15326a8f65a45..b3eb71f93cdfb 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md +++ b/crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md @@ -91,6 +91,8 @@ class C: ... ## Union of classes ```py +from typing import Union + class BasicUser: ... class ProUser: ... @@ -103,6 +105,24 @@ def get_user() -> type[BasicUser | ProUser | A.B.C]: # revealed: type[BasicUser] | type[ProUser] | type[C] reveal_type(get_user()) + +def get_user_old_style() -> type[Union[BasicUser, ProUser, A.B.C]]: + return BasicUser + +# revealed: type[BasicUser] | type[ProUser] | type[C] +reveal_type(get_user_old_style()) + +def get_user_nested() -> type[Union[BasicUser, Union[ProUser, A.B.C]]]: + return BasicUser + +# revealed: type[BasicUser] | type[ProUser] | type[C] +reveal_type(get_user_nested()) + +def get_user_mixed() -> type[BasicUser | Union[ProUser, A.B.C]]: + return BasicUser + +# revealed: type[BasicUser] | type[ProUser] | type[C] +reveal_type(get_user_mixed()) ``` ## Illegal parameters diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 0e2fe388f6ae6..2347619380c26 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -4684,6 +4684,29 @@ impl<'db> TypeInferenceBuilder<'db> { ); Type::Unknown } + ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) + if matches!( + self.infer_expression(value), + Type::KnownInstance(KnownInstanceType::Union) + ) => + { + let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() else { + return Type::Unknown; + }; + + if elts.len() < 2 { + return Type::Unknown; + } + + let union_ty = UnionType::from_elements( + self.db, + elts.iter() + .map(|it| self.infer_subclass_of_type_expression(it)), + ); + self.store_expression_type(slice, union_ty); + + union_ty + } // TODO: subscripts, etc. _ => { self.infer_type_expression(slice); From e6e9e8fb72dfae6fe72398f5fdc93e93f00e445f Mon Sep 17 00:00:00 2001 From: InSyncWithFoo Date: Mon, 9 Dec 2024 05:50:29 +0000 Subject: [PATCH 2/6] Add `N806.py` to `KNOWN_FAILURES` --- crates/red_knot_workspace/tests/check.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/red_knot_workspace/tests/check.rs b/crates/red_knot_workspace/tests/check.rs index 733bf2efdd0d3..a6c5e33cd353c 100644 --- a/crates/red_knot_workspace/tests/check.rs +++ b/crates/red_knot_workspace/tests/check.rs @@ -274,4 +274,6 @@ const KNOWN_FAILURES: &[(&str, bool, bool)] = &[ // related to circular references in f-string annotations (invalid syntax) ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_15.py", true, true), ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_14.py", false, true), + // related to `collections.namedtuple` + ("crates/ruff_linter/resources/test/fixtures/pep8_naming/N806.py", true, false), ]; From 3ed31db96761029bdd83240f373681bcdcd0bfc8 Mon Sep 17 00:00:00 2001 From: InSync Date: Mon, 9 Dec 2024 13:09:01 +0700 Subject: [PATCH 3/6] Fix --- crates/red_knot_workspace/tests/check.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/red_knot_workspace/tests/check.rs b/crates/red_knot_workspace/tests/check.rs index a6c5e33cd353c..7a30cc18e1508 100644 --- a/crates/red_knot_workspace/tests/check.rs +++ b/crates/red_knot_workspace/tests/check.rs @@ -275,5 +275,5 @@ const KNOWN_FAILURES: &[(&str, bool, bool)] = &[ ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_15.py", true, true), ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_14.py", false, true), // related to `collections.namedtuple` - ("crates/ruff_linter/resources/test/fixtures/pep8_naming/N806.py", true, false), + ("crates/ruff_linter/resources/test/fixtures/pep8_naming/N806.py", false, true), ]; From fc19763e346ba29a10e919e799f7046a5e1e3c3e Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 9 Dec 2024 12:29:35 +0000 Subject: [PATCH 4/6] fixes --- .../resources/mdtest/type_of/basic.md | 52 +++++++++++++------ .../src/types/infer.rs | 50 ++++++++++-------- crates/red_knot_workspace/tests/check.rs | 2 - 3 files changed, 65 insertions(+), 39 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md b/crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md index b3eb71f93cdfb..03918d3214e73 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md +++ b/crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md @@ -88,11 +88,9 @@ reveal_type(f()) # revealed: @Todo(unsupported type[X] special form) class C: ... ``` -## Union of classes +## New-style union of classes ```py -from typing import Union - class BasicUser: ... class ProUser: ... @@ -105,24 +103,48 @@ def get_user() -> type[BasicUser | ProUser | A.B.C]: # revealed: type[BasicUser] | type[ProUser] | type[C] reveal_type(get_user()) +``` -def get_user_old_style() -> type[Union[BasicUser, ProUser, A.B.C]]: - return BasicUser +## Old-style union of classes -# revealed: type[BasicUser] | type[ProUser] | type[C] -reveal_type(get_user_old_style()) +```py +from typing import Union -def get_user_nested() -> type[Union[BasicUser, Union[ProUser, A.B.C]]]: - return BasicUser +class BasicUser: ... +class ProUser: ... -# revealed: type[BasicUser] | type[ProUser] | type[C] -reveal_type(get_user_nested()) +class A: + class B: + class C: ... -def get_user_mixed() -> type[BasicUser | Union[ProUser, A.B.C]]: - return BasicUser +def f( + a: type[Union[BasicUser, ProUser, A.B.C]], + b: type[Union[str]], + c: type[Union[BasicUser, Union[ProUser, A.B.C]]] +): + reveal_type(a) # revealed: type[BasicUser] | type[ProUser] | type[C] + reveal_type(b) # revealed: type[str] + reveal_type(c) # revealed: type[BasicUser] | type[ProUser] | type[C] +``` -# revealed: type[BasicUser] | type[ProUser] | type[C] -reveal_type(get_user_mixed()) +## New-style and old-style unions in combination + +```py +from typing import Union + +class BasicUser: ... +class ProUser: ... + +class A: + class B: + class C: ... + +def f( + a: type[BasicUser | Union[ProUser, A.B.C]], + b: type[Union[BasicUser | Union[ProUser, A.B.C | str]]] +): + reveal_type(a) # revealed: type[BasicUser] | type[ProUser] | type[C] + reveal_type(b) # revealed: type[BasicUser] | type[ProUser] | type[C] | type[str] ``` ## Illegal parameters diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 2347619380c26..45a2b9b671547 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -4684,29 +4684,35 @@ impl<'db> TypeInferenceBuilder<'db> { ); Type::Unknown } - ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) - if matches!( - self.infer_expression(value), - Type::KnownInstance(KnownInstanceType::Union) - ) => - { - let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() else { - return Type::Unknown; - }; - - if elts.len() < 2 { - return Type::Unknown; + ast::Expr::Subscript(ast::ExprSubscript { + value, + slice: parameters, + .. + }) => match self.infer_expression(value) { + Type::KnownInstance(KnownInstanceType::Union) => { + let ty = match &**parameters { + ast::Expr::Tuple(tuple) => { + let ty = UnionType::from_elements( + self.db, + tuple + .iter() + .map(|element| self.infer_subclass_of_type_expression(element)), + ); + self.store_expression_type(parameters, ty); + ty + } + _ => self.infer_subclass_of_type_expression(parameters), + }; + self.store_expression_type(slice, ty); + ty } - - let union_ty = UnionType::from_elements( - self.db, - elts.iter() - .map(|it| self.infer_subclass_of_type_expression(it)), - ); - self.store_expression_type(slice, union_ty); - - union_ty - } + _ => { + self.infer_type_expression(parameters); + let ty = todo_type!("unsupported nested subscript in type[X]"); + self.store_expression_type(slice, ty); + ty + } + }, // TODO: subscripts, etc. _ => { self.infer_type_expression(slice); diff --git a/crates/red_knot_workspace/tests/check.rs b/crates/red_knot_workspace/tests/check.rs index 7a30cc18e1508..733bf2efdd0d3 100644 --- a/crates/red_knot_workspace/tests/check.rs +++ b/crates/red_knot_workspace/tests/check.rs @@ -274,6 +274,4 @@ const KNOWN_FAILURES: &[(&str, bool, bool)] = &[ // related to circular references in f-string annotations (invalid syntax) ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_15.py", true, true), ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_14.py", false, true), - // related to `collections.namedtuple` - ("crates/ruff_linter/resources/test/fixtures/pep8_naming/N806.py", false, true), ]; From bbf2ca33a6024253d96c00a2acc143647eb8cc4f Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 9 Dec 2024 12:31:57 +0000 Subject: [PATCH 5/6] lint --- .../resources/mdtest/type_of/basic.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md b/crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md index 03918d3214e73..642e070c7934a 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md +++ b/crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md @@ -117,11 +117,7 @@ class A: class B: class C: ... -def f( - a: type[Union[BasicUser, ProUser, A.B.C]], - b: type[Union[str]], - c: type[Union[BasicUser, Union[ProUser, A.B.C]]] -): +def f(a: type[Union[BasicUser, ProUser, A.B.C]], b: type[Union[str]], c: type[Union[BasicUser, Union[ProUser, A.B.C]]]): reveal_type(a) # revealed: type[BasicUser] | type[ProUser] | type[C] reveal_type(b) # revealed: type[str] reveal_type(c) # revealed: type[BasicUser] | type[ProUser] | type[C] @@ -139,10 +135,7 @@ class A: class B: class C: ... -def f( - a: type[BasicUser | Union[ProUser, A.B.C]], - b: type[Union[BasicUser | Union[ProUser, A.B.C | str]]] -): +def f(a: type[BasicUser | Union[ProUser, A.B.C]], b: type[Union[BasicUser | Union[ProUser, A.B.C | str]]]): reveal_type(a) # revealed: type[BasicUser] | type[ProUser] | type[C] reveal_type(b) # revealed: type[BasicUser] | type[ProUser] | type[C] | type[str] ``` From 732bf55be309d3ec82ef295dd7018d00c60b89df Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 9 Dec 2024 12:40:16 +0000 Subject: [PATCH 6/6] nit --- .../src/types/infer.rs | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 45a2b9b671547..ccd90bb6e261b 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -4688,9 +4688,9 @@ impl<'db> TypeInferenceBuilder<'db> { value, slice: parameters, .. - }) => match self.infer_expression(value) { - Type::KnownInstance(KnownInstanceType::Union) => { - let ty = match &**parameters { + }) => { + let parameters_ty = match self.infer_expression(value) { + Type::KnownInstance(KnownInstanceType::Union) => match &**parameters { ast::Expr::Tuple(tuple) => { let ty = UnionType::from_elements( self.db, @@ -4702,17 +4702,15 @@ impl<'db> TypeInferenceBuilder<'db> { ty } _ => self.infer_subclass_of_type_expression(parameters), - }; - self.store_expression_type(slice, ty); - ty - } - _ => { - self.infer_type_expression(parameters); - let ty = todo_type!("unsupported nested subscript in type[X]"); - self.store_expression_type(slice, ty); - ty - } - }, + }, + _ => { + self.infer_type_expression(parameters); + todo_type!("unsupported nested subscript in type[X]") + } + }; + self.store_expression_type(slice, parameters_ty); + parameters_ty + } // TODO: subscripts, etc. _ => { self.infer_type_expression(slice);