From 37f560762766503f8e76a7f8f269db1ba98fe335 Mon Sep 17 00:00:00 2001 From: Glyphack Date: Sun, 15 Dec 2024 12:59:42 +0100 Subject: [PATCH 01/38] Allow assigning ellipsis literal as parameter default value Resolves 14840 --- .../resources/mdtest/stubs/ellipsis.md | 31 +++++++++++++++++++ .../src/types/infer.rs | 5 +++ 2 files changed, 36 insertions(+) create mode 100644 crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md new file mode 100644 index 0000000000000..cdc83a782f7a3 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -0,0 +1,31 @@ +# Ellipsis + +## Function and Methods + +For default values the ellipsis literal `...` can be used. + +```py +from typing import Dict + +def f(x: int = ...) -> None: ... +def f2(x: Dict = ...) -> None: ... + +# error: [invalid-assignment] "Object of type `EllipsisType | ellipsis` is not assignable to `int`" +y: int = ... +``` + +## Class and Module Level Attributes + +Using ellipsis literal for classes and module level attributes is unnecessary and results in an +error. + +- + +```py +# error: [invalid-assignment] "Object of type `EllipsisType | ellipsis` is not assignable to `float`" +y: float = ... + +class Foo: + # error: [invalid-assignment] "Object of type `EllipsisType | ellipsis` is not assignable to `int`" + y: int = ... +``` diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 8650d7f436a9a..1c714d23ba247 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -1174,6 +1174,11 @@ impl<'db> TypeInferenceBuilder<'db> { let inferred_ty = if let Some(default_ty) = default_ty { if default_ty.is_assignable_to(self.db(), declared_ty) { UnionType::from_elements(self.db(), [declared_ty, default_ty]) + } else if default + .as_ref() + .is_some_and(|d| d.is_ellipsis_literal_expr()) + { + declared_ty } else { self.context.report_lint( &INVALID_PARAMETER_DEFAULT, From 69fe38197517d99f7720bf66bdd270a0008e05e2 Mon Sep 17 00:00:00 2001 From: Glyphack Date: Sun, 15 Dec 2024 20:23:01 +0100 Subject: [PATCH 02/38] Add Ellipsis symbol to tests --- .../resources/mdtest/stubs/ellipsis.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index cdc83a782f7a3..40cba8f7b35bd 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -14,6 +14,17 @@ def f2(x: Dict = ...) -> None: ... y: int = ... ``` +## Use of Ellipsis Symbol + +When the ellipsis symbol is used as default value the assignment is checked. + +```py +from typing import Dict + +# error: [invalid-parameter-default] "Default value of type `EllipsisType | ellipsis` is not assignable to annotated parameter type `int`" +def f(x: int = Ellipsis) -> None: ... +``` + ## Class and Module Level Attributes Using ellipsis literal for classes and module level attributes is unnecessary and results in an From 3d69045305b41cf797486053c3bc86312d5242f9 Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Mon, 16 Dec 2024 13:24:00 -0800 Subject: [PATCH 03/38] Update crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md Co-authored-by: Carl Meyer --- .../resources/mdtest/stubs/ellipsis.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index 40cba8f7b35bd..6f64c47436a75 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -2,9 +2,9 @@ ## Function and Methods -For default values the ellipsis literal `...` can be used. +For default values the ellipsis literal `...` can be used, in a stub file only. -```py +```py path=test.pyi from typing import Dict def f(x: int = ...) -> None: ... From 3b730baac67b1e6b41ed598539dcad98722907a3 Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Mon, 16 Dec 2024 13:24:10 -0800 Subject: [PATCH 04/38] Update crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md Co-authored-by: Carl Meyer --- .../resources/mdtest/stubs/ellipsis.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index 6f64c47436a75..0d5a67c0359fc 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -5,10 +5,8 @@ For default values the ellipsis literal `...` can be used, in a stub file only. ```py path=test.pyi -from typing import Dict - def f(x: int = ...) -> None: ... -def f2(x: Dict = ...) -> None: ... +def f2(x: dict = ...) -> None: ... # error: [invalid-assignment] "Object of type `EllipsisType | ellipsis` is not assignable to `int`" y: int = ... From 39399ce6c8afed8dbc295bfc07d047be99089ea6 Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Mon, 16 Dec 2024 13:24:16 -0800 Subject: [PATCH 05/38] Update crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md Co-authored-by: Carl Meyer --- .../red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index 0d5a67c0359fc..4ffa382f97608 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -17,8 +17,6 @@ y: int = ... When the ellipsis symbol is used as default value the assignment is checked. ```py -from typing import Dict - # error: [invalid-parameter-default] "Default value of type `EllipsisType | ellipsis` is not assignable to annotated parameter type `int`" def f(x: int = Ellipsis) -> None: ... ``` From 3c81c9b73764bf0256524e0a19dd2c50cc7083a8 Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Mon, 16 Dec 2024 13:24:23 -0800 Subject: [PATCH 06/38] Update crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md Co-authored-by: Carl Meyer --- .../red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index 4ffa382f97608..13f1dff92891e 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -28,7 +28,7 @@ error. - -```py +```py path=test.pyi # error: [invalid-assignment] "Object of type `EllipsisType | ellipsis` is not assignable to `float`" y: float = ... From d28b2e7803f9c7be9c5e5f9628494dcce6ec07e5 Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Mon, 16 Dec 2024 13:24:29 -0800 Subject: [PATCH 07/38] Update crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md Co-authored-by: Carl Meyer --- .../red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index 13f1dff92891e..099d2fc6cdb86 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -16,7 +16,7 @@ y: int = ... When the ellipsis symbol is used as default value the assignment is checked. -```py +```py path=test.pyi # error: [invalid-parameter-default] "Default value of type `EllipsisType | ellipsis` is not assignable to annotated parameter type `int`" def f(x: int = Ellipsis) -> None: ... ``` From 342cd2ba4b658421ec684f82f397f50f39f32ed0 Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Mon, 16 Dec 2024 13:24:43 -0800 Subject: [PATCH 08/38] Update crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md Co-authored-by: Carl Meyer --- .../resources/mdtest/stubs/ellipsis.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index 099d2fc6cdb86..dd84bab48235a 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -7,9 +7,6 @@ For default values the ellipsis literal `...` can be used, in a stub file only. ```py path=test.pyi def f(x: int = ...) -> None: ... def f2(x: dict = ...) -> None: ... - -# error: [invalid-assignment] "Object of type `EllipsisType | ellipsis` is not assignable to `int`" -y: int = ... ``` ## Use of Ellipsis Symbol From f2d7bcbf914fb36c25d9ba4a49988dcd19e8b4b7 Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Mon, 16 Dec 2024 13:24:49 -0800 Subject: [PATCH 09/38] Update crates/red_knot_python_semantic/src/types/infer.rs Co-authored-by: Carl Meyer --- crates/red_knot_python_semantic/src/types/infer.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 1c714d23ba247..fdb0c53e0265e 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -1174,9 +1174,10 @@ impl<'db> TypeInferenceBuilder<'db> { let inferred_ty = if let Some(default_ty) = default_ty { if default_ty.is_assignable_to(self.db(), declared_ty) { UnionType::from_elements(self.db(), [declared_ty, default_ty]) - } else if default - .as_ref() - .is_some_and(|d| d.is_ellipsis_literal_expr()) + } else if self.file.is_stub(self.db.upcast()) + && default + .as_ref() + .is_some_and(|d| d.is_ellipsis_literal_expr()) { declared_ty } else { From 11519215965ba1700cffca215e9e117da13e071c Mon Sep 17 00:00:00 2001 From: Glyphack Date: Thu, 19 Dec 2024 21:26:59 +0100 Subject: [PATCH 10/38] Allow usage of ellipsis literal in all scopes --- .../resources/mdtest/stubs/ellipsis.md | 39 ++++++++++++------- .../src/types/infer.rs | 31 +++++++++++---- 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index dd84bab48235a..1fe385dc9788b 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -9,27 +9,38 @@ def f(x: int = ...) -> None: ... def f2(x: dict = ...) -> None: ... ``` -## Use of Ellipsis Symbol +## Class and Module Level Attributes -When the ellipsis symbol is used as default value the assignment is checked. +For default values the ellipsis literal can be used in classes and module level attributes, in a +stub file only. ```py path=test.pyi -# error: [invalid-parameter-default] "Default value of type `EllipsisType | ellipsis` is not assignable to annotated parameter type `int`" -def f(x: int = Ellipsis) -> None: ... +y: float = ... + +class Foo: + y: int = ... ``` -## Class and Module Level Attributes +## Ellipsis Usage In Non Stub File + +Ellipsis can only be used in assignment if it's actually assignable to the type it's being assigned +to. + +```py +# error: [invalid-parameter-default] "Default value of type `EllipsisType | ellipsis` is not assignable to annotated parameter type `int`" +def f(x: int = ...) -> None: ... -Using ellipsis literal for classes and module level attributes is unnecessary and results in an -error. +# error: [invalid-assignment] "Object of type `EllipsisType | ellipsis` is not assignable to `int`" +a: int = ... +b = ... +reveal_type(b) # revealed: EllipsisType | ellipsis +``` -- +## Use of Ellipsis Symbol -```py path=test.pyi -# error: [invalid-assignment] "Object of type `EllipsisType | ellipsis` is not assignable to `float`" -y: float = ... +When the ellipsis symbol is used as default value the assignment is checked. -class Foo: - # error: [invalid-assignment] "Object of type `EllipsisType | ellipsis` is not assignable to `int`" - y: int = ... +```py path=test.pyi +# error: [invalid-parameter-default] "Default value of type `EllipsisType | ellipsis` is not assignable to annotated parameter type `int`" +def f(x: int = Ellipsis) -> None: ... ``` diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index fdb0c53e0265e..6bd43390e3944 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -1174,7 +1174,7 @@ impl<'db> TypeInferenceBuilder<'db> { let inferred_ty = if let Some(default_ty) = default_ty { if default_ty.is_assignable_to(self.db(), declared_ty) { UnionType::from_elements(self.db(), [declared_ty, default_ty]) - } else if self.file.is_stub(self.db.upcast()) + } else if self.file().is_stub(self.db().upcast()) && default .as_ref() .is_some_and(|d| d.is_ellipsis_literal_expr()) @@ -1902,7 +1902,13 @@ impl<'db> TypeInferenceBuilder<'db> { let name_ast_id = name.scoped_expression_id(self.db(), self.scope()); unpacked.get(name_ast_id).unwrap_or(Type::Unknown) } - TargetKind::Name => value_ty, + TargetKind::Name => { + if self.file().is_stub(self.db().upcast()) && value.is_ellipsis_literal_expr() { + Type::Any + } else { + value_ty + } + } }; if let Some(known_instance) = @@ -1969,12 +1975,21 @@ impl<'db> TypeInferenceBuilder<'db> { if let Some(value) = value.as_deref() { let value_ty = self.infer_expression(value); - self.add_declaration_with_binding( - assignment.into(), - definition, - annotation_ty, - value_ty, - ); + if self.file().is_stub(self.db().upcast()) { + self.add_declaration_with_binding( + assignment.into(), + definition, + annotation_ty, + Type::Unknown, + ); + } else { + self.add_declaration_with_binding( + assignment.into(), + definition, + annotation_ty, + value_ty, + ); + } } else { self.add_declaration(assignment.into(), definition, annotation_ty); } From b5791e1d14a5185473f88f772d1f3cca547cdff2 Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Fri, 20 Dec 2024 09:24:00 +0100 Subject: [PATCH 11/38] Update crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md Co-authored-by: Carl Meyer --- .../resources/mdtest/stubs/ellipsis.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index 1fe385dc9788b..99a19980639c2 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -2,7 +2,8 @@ ## Function and Methods -For default values the ellipsis literal `...` can be used, in a stub file only. +The ellipsis literal `...` can be used as a placeholder default value for a function parameter, + in a stub file only, regardless of the type of the parameter. ```py path=test.pyi def f(x: int = ...) -> None: ... From 539d83c8fadd29b32dd03abd82123cdc2999f712 Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Fri, 20 Dec 2024 09:24:07 +0100 Subject: [PATCH 12/38] Update crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md Co-authored-by: Carl Meyer --- .../red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index 99a19980639c2..ddd56eaec79ac 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -12,7 +12,7 @@ def f2(x: dict = ...) -> None: ... ## Class and Module Level Attributes -For default values the ellipsis literal can be used in classes and module level attributes, in a +The ellipsis literal can be assigned to a class or module attribute, regardless of its type, in a stub file only. ```py path=test.pyi From f31c237a601c79bbab733e90f2f7d10752ed5d3c Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Fri, 20 Dec 2024 09:24:20 +0100 Subject: [PATCH 13/38] Update crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md Co-authored-by: Carl Meyer --- .../resources/mdtest/stubs/ellipsis.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index ddd56eaec79ac..dbe6afe618ca0 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -6,8 +6,10 @@ The ellipsis literal `...` can be used as a placeholder default value for a func in a stub file only, regardless of the type of the parameter. ```py path=test.pyi -def f(x: int = ...) -> None: ... -def f2(x: dict = ...) -> None: ... +def f(x: int = ...) -> None: + reveal_type(x) # revealed: int +def f2(x: str = ...) -> None: + reveal_type(x) # revealed: str ``` ## Class and Module Level Attributes From e4ce7d353a06cb893c6e732fa601f35d7ce95fd8 Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Fri, 20 Dec 2024 09:24:29 +0100 Subject: [PATCH 14/38] Update crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md Co-authored-by: Carl Meyer --- .../resources/mdtest/stubs/ellipsis.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index dbe6afe618ca0..89993f1af3e01 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -18,10 +18,13 @@ The ellipsis literal can be assigned to a class or module attribute, regardless stub file only. ```py path=test.pyi -y: float = ... +y: bytes = ... +reveal_type(y) # revealed: bytes class Foo: y: int = ... + +reveal_type(Foo.y) # revealed: int ``` ## Ellipsis Usage In Non Stub File From bd216c1383d579394bd37322a818aa9c6824c2ea Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Fri, 20 Dec 2024 09:24:38 +0100 Subject: [PATCH 15/38] Update crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md Co-authored-by: Carl Meyer --- .../resources/mdtest/stubs/ellipsis.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index 89993f1af3e01..433428ae5d02e 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -29,8 +29,8 @@ reveal_type(Foo.y) # revealed: int ## Ellipsis Usage In Non Stub File -Ellipsis can only be used in assignment if it's actually assignable to the type it's being assigned -to. +In a non-stub file, there's no special treatment of ellipsis literals. An ellipsis literal can only be assigned if +`EllipsisType` is actually assignable to the annotated type. ```py # error: [invalid-parameter-default] "Default value of type `EllipsisType | ellipsis` is not assignable to annotated parameter type `int`" From c0709f079a7fbe630a0d634d92e103a8a7920d44 Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Fri, 20 Dec 2024 09:24:47 +0100 Subject: [PATCH 16/38] Update crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md Co-authored-by: Carl Meyer --- .../red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index 433428ae5d02e..645569427dc3b 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -44,7 +44,7 @@ reveal_type(b) # revealed: EllipsisType | ellipsis ## Use of Ellipsis Symbol -When the ellipsis symbol is used as default value the assignment is checked. +There is no special treatment of the builtin name `Ellipsis`, only of `...` literals. ```py path=test.pyi # error: [invalid-parameter-default] "Default value of type `EllipsisType | ellipsis` is not assignable to annotated parameter type `int`" From 98dab34cab4e31433150d4e148ee7bb938ffe16d Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Fri, 20 Dec 2024 09:25:15 +0100 Subject: [PATCH 17/38] Update crates/red_knot_python_semantic/src/types/infer.rs Co-authored-by: Carl Meyer --- .../src/types/infer.rs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 6bd43390e3944..8cb6c0039db4c 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -1975,21 +1975,17 @@ impl<'db> TypeInferenceBuilder<'db> { if let Some(value) = value.as_deref() { let value_ty = self.infer_expression(value); - if self.file().is_stub(self.db().upcast()) { - self.add_declaration_with_binding( - assignment.into(), - definition, - annotation_ty, - Type::Unknown, - ); + let value_ty = if self.file().is_stub(self.db().upcast()) && value.is_ellipsis_literal_expr() { + annotation_ty } else { - self.add_declaration_with_binding( - assignment.into(), - definition, - annotation_ty, - value_ty, - ); + value_ty } + self.add_declaration_with_binding( + assignment.into(), + definition, + annotation_ty, + value_ty, + ); } else { self.add_declaration(assignment.into(), definition, annotation_ty); } From 13f91d7f04468f1609bc85e2c00bf8d82af6d1b8 Mon Sep 17 00:00:00 2001 From: Glyphack Date: Mon, 30 Dec 2024 12:41:47 +0100 Subject: [PATCH 18/38] Infer Type::Unknown for a variable with value `...` --- .../resources/mdtest/stubs/ellipsis.md | 4 +++- crates/red_knot_python_semantic/src/types/infer.rs | 11 ++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index 645569427dc3b..dee9849257ad1 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -20,10 +20,12 @@ stub file only. ```py path=test.pyi y: bytes = ... reveal_type(y) # revealed: bytes +x = ... +reveal_type(x) # revealed: Unknown class Foo: y: int = ... - + reveal_type(Foo.y) # revealed: int ``` diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 8cb6c0039db4c..63f1fe231c4ac 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -435,6 +435,11 @@ impl<'db> TypeInferenceBuilder<'db> { matches!(self.region, InferenceRegion::Deferred(_)) || self.deferred_state.is_deferred() } + /// Are we currently inferring types in a stub file? + fn is_stub(&self) -> bool { + return self.file().is_stub(self.db().upcast()); + } + /// Get the already-inferred type of an expression node. /// /// ## Panics @@ -1174,7 +1179,7 @@ impl<'db> TypeInferenceBuilder<'db> { let inferred_ty = if let Some(default_ty) = default_ty { if default_ty.is_assignable_to(self.db(), declared_ty) { UnionType::from_elements(self.db(), [declared_ty, default_ty]) - } else if self.file().is_stub(self.db().upcast()) + } else if self.is_stub() && default .as_ref() .is_some_and(|d| d.is_ellipsis_literal_expr()) @@ -1903,8 +1908,8 @@ impl<'db> TypeInferenceBuilder<'db> { unpacked.get(name_ast_id).unwrap_or(Type::Unknown) } TargetKind::Name => { - if self.file().is_stub(self.db().upcast()) && value.is_ellipsis_literal_expr() { - Type::Any + if self.is_stub() && value.is_ellipsis_literal_expr() { + Type::Unknown } else { value_ty } From c65731b465fd8a4e97e7923077532915eb4bf78e Mon Sep 17 00:00:00 2001 From: Glyphack Date: Mon, 30 Dec 2024 14:34:17 +0100 Subject: [PATCH 19/38] Add a test for annotated assignments in stub files --- .../resources/mdtest/assignment/annotations.md | 7 +++++++ crates/red_knot_python_semantic/src/types/infer.rs | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md b/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md index f696cd4ea414f..3c9397e905be4 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md +++ b/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md @@ -122,3 +122,10 @@ class Foo: ... x = Foo() reveal_type(x) # revealed: Foo ``` + +## Annotations in stub files are inferred correctly + +```pyi path=main.pyi +x: int = 1 +reveal_type(x) # revealed: Literal[1] +``` diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 63f1fe231c4ac..f5c1e3d9c9583 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -1980,11 +1980,11 @@ impl<'db> TypeInferenceBuilder<'db> { if let Some(value) = value.as_deref() { let value_ty = self.infer_expression(value); - let value_ty = if self.file().is_stub(self.db().upcast()) && value.is_ellipsis_literal_expr() { + let value_ty = if self.is_stub() && value.is_ellipsis_literal_expr() { annotation_ty } else { value_ty - } + }; self.add_declaration_with_binding( assignment.into(), definition, From 2753d878e03a796a9c525dc74e6d42c32c477c92 Mon Sep 17 00:00:00 2001 From: Glyphack Date: Mon, 30 Dec 2024 15:05:31 +0100 Subject: [PATCH 20/38] Allow unpacking `...` in stub files --- .../resources/mdtest/stubs/ellipsis.md | 25 +++++++++++++------ .../src/types/unpacker.rs | 17 +++++++++++-- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index dee9849257ad1..91b6c9b26f108 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -2,12 +2,13 @@ ## Function and Methods -The ellipsis literal `...` can be used as a placeholder default value for a function parameter, - in a stub file only, regardless of the type of the parameter. +The ellipsis literal `...` can be used as a placeholder default value for a function parameter, in a +stub file only, regardless of the type of the parameter. ```py path=test.pyi def f(x: int = ...) -> None: reveal_type(x) # revealed: int + def f2(x: str = ...) -> None: reveal_type(x) # revealed: str ``` @@ -29,19 +30,27 @@ class Foo: reveal_type(Foo.y) # revealed: int ``` +## Assigning Ellipsis Literal to Multiple Targets + +```py path=test.pyi +x, y = ... +reveal_type(x) # revealed: Unknown +reveal_type(y) # revealed: Unknown +``` + ## Ellipsis Usage In Non Stub File -In a non-stub file, there's no special treatment of ellipsis literals. An ellipsis literal can only be assigned if -`EllipsisType` is actually assignable to the annotated type. +In a non-stub file, there's no special treatment of ellipsis literals. An ellipsis literal can only +be assigned if `EllipsisType` is actually assignable to the annotated type. ```py -# error: [invalid-parameter-default] "Default value of type `EllipsisType | ellipsis` is not assignable to annotated parameter type `int`" +# error: 7 [invalid-parameter-default] "Default value of type `ellipsis` is not assignable to annotated parameter type `int`" def f(x: int = ...) -> None: ... -# error: [invalid-assignment] "Object of type `EllipsisType | ellipsis` is not assignable to `int`" +# error: 1 [invalid-assignment] "Object of type `ellipsis` is not assignable to `int`" a: int = ... b = ... -reveal_type(b) # revealed: EllipsisType | ellipsis +reveal_type(b) # revealed: ellipsis ``` ## Use of Ellipsis Symbol @@ -49,6 +58,6 @@ reveal_type(b) # revealed: EllipsisType | ellipsis There is no special treatment of the builtin name `Ellipsis`, only of `...` literals. ```py path=test.pyi -# error: [invalid-parameter-default] "Default value of type `EllipsisType | ellipsis` is not assignable to annotated parameter type `int`" +# error: 7 [invalid-parameter-default] "Default value of type `ellipsis` is not assignable to annotated parameter type `int`" def f(x: int = Ellipsis) -> None: ... ``` diff --git a/crates/red_knot_python_semantic/src/types/unpacker.rs b/crates/red_knot_python_semantic/src/types/unpacker.rs index a7da2b42ea356..533e5ef5a8336 100644 --- a/crates/red_knot_python_semantic/src/types/unpacker.rs +++ b/crates/red_knot_python_semantic/src/types/unpacker.rs @@ -38,13 +38,26 @@ impl<'db> Unpacker<'db> { /// Unpack the value to the target expression. pub(crate) fn unpack(&mut self, target: &ast::Expr, value: UnpackValue<'db>) { debug_assert!( - matches!(target, ast::Expr::List(_) | ast::Expr::Tuple(_)), - "Unpacking target must be a list or tuple expression" + matches!( + target, + ast::Expr::List(_) | ast::Expr::Tuple(_) | ast::Expr::EllipsisLiteral(_) + ), + "Unpacking target must be a list or tuple or ellipsis literal expression" ); let mut value_ty = infer_expression_types(self.db(), value.expression()) .expression_ty(value.scoped_expression_id(self.db(), self.scope)); + let file = value.expression().file(self.db()); + let is_ellipsis_literal = value + .expression() + .node_ref(self.db()) + .is_ellipsis_literal_expr(); + // Unpacking Ellipsis Literal is allowed in stub files. + if file.is_stub(self.db().upcast()) && is_ellipsis_literal { + value_ty = Type::Unknown; + } + if value.is_iterable() { // If the value is an iterable, then the type that needs to be unpacked is the iterator // type. From 4f8ae4690b7ecf28458fe0f61b019d45bce45b9c Mon Sep 17 00:00:00 2001 From: Glyphack Date: Mon, 30 Dec 2024 15:26:27 +0100 Subject: [PATCH 21/38] Clippy fix --- crates/red_knot_python_semantic/src/types/infer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index f5c1e3d9c9583..75171b9428d90 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -437,7 +437,7 @@ impl<'db> TypeInferenceBuilder<'db> { /// Are we currently inferring types in a stub file? fn is_stub(&self) -> bool { - return self.file().is_stub(self.db().upcast()); + self.file().is_stub(self.db().upcast()) } /// Get the already-inferred type of an expression node. From dd6396b1ea05ab9c85c87253824b6f7357865d8a Mon Sep 17 00:00:00 2001 From: Glyphack Date: Mon, 30 Dec 2024 19:35:59 +0100 Subject: [PATCH 22/38] Do not allow for loops to iterate over ellipsis literal --- .../resources/mdtest/stubs/ellipsis.md | 19 ++++++++++---- .../src/types/infer.rs | 12 ++++++--- .../src/types/unpacker.rs | 25 +++++++++++-------- 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index 91b6c9b26f108..b827cb5c05cbe 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -1,6 +1,6 @@ # Ellipsis -## Function and Methods +## Function and methods The ellipsis literal `...` can be used as a placeholder default value for a function parameter, in a stub file only, regardless of the type of the parameter. @@ -13,7 +13,7 @@ def f2(x: str = ...) -> None: reveal_type(x) # revealed: str ``` -## Class and Module Level Attributes +## Class and module level attributes The ellipsis literal can be assigned to a class or module attribute, regardless of its type, in a stub file only. @@ -30,7 +30,7 @@ class Foo: reveal_type(Foo.y) # revealed: int ``` -## Assigning Ellipsis Literal to Multiple Targets +## Unpacking ellipsis literal in assignment ```py path=test.pyi x, y = ... @@ -38,7 +38,16 @@ reveal_type(x) # revealed: Unknown reveal_type(y) # revealed: Unknown ``` -## Ellipsis Usage In Non Stub File +## Unpacking ellipsis literal in for loops + +```py path=test.pyi +# error: [not-iterable] "Object of type `ellipsis` is not iterable" +for a, b in ...: + reveal_type(a) # revealed: Unknown + reveal_type(b) # revealed: Unknown +``` + +## Ellipsis usage in non stub file In a non-stub file, there's no special treatment of ellipsis literals. An ellipsis literal can only be assigned if `EllipsisType` is actually assignable to the annotated type. @@ -53,7 +62,7 @@ b = ... reveal_type(b) # revealed: ellipsis ``` -## Use of Ellipsis Symbol +## Use of ellipsis symbol There is no special treatment of the builtin name `Ellipsis`, only of `...` literals. diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 75171b9428d90..41aff004345ae 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -195,13 +195,17 @@ pub(crate) fn infer_expression_types<'db>( /// type of the variables involved in this unpacking along with any violations that are detected /// during this unpacking. #[salsa::tracked(return_ref)] -fn infer_unpack_types<'db>(db: &'db dyn Db, unpack: Unpack<'db>) -> UnpackResult<'db> { +fn infer_unpack_types<'db>( + db: &'db dyn Db, + unpack: Unpack<'db>, + is_assignment: bool, +) -> UnpackResult<'db> { let file = unpack.file(db); let _span = tracing::trace_span!("infer_unpack_types", range=?unpack.range(db), file=%file.path(db)) .entered(); - let mut unpacker = Unpacker::new(db, unpack.scope(db)); + let mut unpacker = Unpacker::new(db, unpack.scope(db), is_assignment); unpacker.unpack(unpack.target(db), unpack.value(db)); unpacker.finish() } @@ -1897,7 +1901,7 @@ impl<'db> TypeInferenceBuilder<'db> { let mut target_ty = match assignment.target() { TargetKind::Sequence(unpack) => { - let unpacked = infer_unpack_types(self.db(), unpack); + let unpacked = infer_unpack_types(self.db(), unpack, true); // Only copy the diagnostics if this is the first assignment to avoid duplicating the // unpack assignments. if assignment.is_first() { @@ -2163,7 +2167,7 @@ impl<'db> TypeInferenceBuilder<'db> { } else { match for_stmt.target() { TargetKind::Sequence(unpack) => { - let unpacked = infer_unpack_types(self.db(), unpack); + let unpacked = infer_unpack_types(self.db(), unpack, false); if for_stmt.is_first() { self.context.extend(unpacked); } diff --git a/crates/red_knot_python_semantic/src/types/unpacker.rs b/crates/red_knot_python_semantic/src/types/unpacker.rs index 533e5ef5a8336..8df920e0611d1 100644 --- a/crates/red_knot_python_semantic/src/types/unpacker.rs +++ b/crates/red_knot_python_semantic/src/types/unpacker.rs @@ -20,14 +20,18 @@ pub(crate) struct Unpacker<'db> { context: InferContext<'db>, scope: ScopeId<'db>, targets: FxHashMap>, + /// This flag determines if we are unpacking for assignment or not. + /// In stubs files, assigning ellipsis literal to multiple targets is allowed. + is_assignment: bool, } impl<'db> Unpacker<'db> { - pub(crate) fn new(db: &'db dyn Db, scope: ScopeId<'db>) -> Self { + pub(crate) fn new(db: &'db dyn Db, scope: ScopeId<'db>, is_assignment: bool) -> Self { Self { context: InferContext::new(db, scope), targets: FxHashMap::default(), scope, + is_assignment, } } @@ -37,14 +41,6 @@ impl<'db> Unpacker<'db> { /// Unpack the value to the target expression. pub(crate) fn unpack(&mut self, target: &ast::Expr, value: UnpackValue<'db>) { - debug_assert!( - matches!( - target, - ast::Expr::List(_) | ast::Expr::Tuple(_) | ast::Expr::EllipsisLiteral(_) - ), - "Unpacking target must be a list or tuple or ellipsis literal expression" - ); - let mut value_ty = infer_expression_types(self.db(), value.expression()) .expression_ty(value.scoped_expression_id(self.db(), self.scope)); @@ -53,9 +49,16 @@ impl<'db> Unpacker<'db> { .expression() .node_ref(self.db()) .is_ellipsis_literal_expr(); - // Unpacking Ellipsis Literal is allowed in stub files. - if file.is_stub(self.db().upcast()) && is_ellipsis_literal { + if file.is_stub(self.db().upcast()) && is_ellipsis_literal && self.is_assignment { value_ty = Type::Unknown; + } else { + debug_assert!( + matches!( + target, + ast::Expr::List(_) | ast::Expr::Tuple(_) | ast::Expr::EllipsisLiteral(_) + ), + "Unpacking target must be a list or tuple or ellipsis literal expression" + ); } if value.is_iterable() { From 30f8f65666108e0f00b02b0b63450450260ea241 Mon Sep 17 00:00:00 2001 From: Glyphack Date: Tue, 31 Dec 2024 15:37:27 +0100 Subject: [PATCH 23/38] Refactor unpacker code for assignment statements --- .../src/types/infer.rs | 6 ++-- .../src/types/unpacker.rs | 30 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 41aff004345ae..76a8e92b9cf2f 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -198,15 +198,15 @@ pub(crate) fn infer_expression_types<'db>( fn infer_unpack_types<'db>( db: &'db dyn Db, unpack: Unpack<'db>, - is_assignment: bool, + is_assignment_stmt: bool, ) -> UnpackResult<'db> { let file = unpack.file(db); let _span = tracing::trace_span!("infer_unpack_types", range=?unpack.range(db), file=%file.path(db)) .entered(); - let mut unpacker = Unpacker::new(db, unpack.scope(db), is_assignment); - unpacker.unpack(unpack.target(db), unpack.value(db)); + let mut unpacker = Unpacker::new(db, unpack.scope(db)); + unpacker.unpack(unpack.target(db), unpack.value(db), is_assignment_stmt); unpacker.finish() } diff --git a/crates/red_knot_python_semantic/src/types/unpacker.rs b/crates/red_knot_python_semantic/src/types/unpacker.rs index 8df920e0611d1..71ebad03b2733 100644 --- a/crates/red_knot_python_semantic/src/types/unpacker.rs +++ b/crates/red_knot_python_semantic/src/types/unpacker.rs @@ -20,18 +20,14 @@ pub(crate) struct Unpacker<'db> { context: InferContext<'db>, scope: ScopeId<'db>, targets: FxHashMap>, - /// This flag determines if we are unpacking for assignment or not. - /// In stubs files, assigning ellipsis literal to multiple targets is allowed. - is_assignment: bool, } impl<'db> Unpacker<'db> { - pub(crate) fn new(db: &'db dyn Db, scope: ScopeId<'db>, is_assignment: bool) -> Self { + pub(crate) fn new(db: &'db dyn Db, scope: ScopeId<'db>) -> Self { Self { context: InferContext::new(db, scope), targets: FxHashMap::default(), scope, - is_assignment, } } @@ -39,25 +35,29 @@ impl<'db> Unpacker<'db> { self.context.db() } - /// Unpack the value to the target expression. - pub(crate) fn unpack(&mut self, target: &ast::Expr, value: UnpackValue<'db>) { + pub(crate) fn unpack( + &mut self, + target: &ast::Expr, + value: UnpackValue<'db>, + is_assignment_stmt: bool, + ) { let mut value_ty = infer_expression_types(self.db(), value.expression()) .expression_ty(value.scoped_expression_id(self.db(), self.scope)); - let file = value.expression().file(self.db()); - let is_ellipsis_literal = value + let is_in_stub_file = value + .expression() + .file(self.db()) + .is_stub(self.db().upcast()); + let value_is_ellipsis_literal = value .expression() .node_ref(self.db()) .is_ellipsis_literal_expr(); - if file.is_stub(self.db().upcast()) && is_ellipsis_literal && self.is_assignment { + if is_assignment_stmt && is_in_stub_file && value_is_ellipsis_literal { value_ty = Type::Unknown; } else { debug_assert!( - matches!( - target, - ast::Expr::List(_) | ast::Expr::Tuple(_) | ast::Expr::EllipsisLiteral(_) - ), - "Unpacking target must be a list or tuple or ellipsis literal expression" + matches!(target, ast::Expr::List(_) | ast::Expr::Tuple(_)), + "Unpacking target must be a list or tuple" ); } From c6521ed9433ccde3f5d0a941007ef5c60b00b8af Mon Sep 17 00:00:00 2001 From: Glyphack Date: Tue, 31 Dec 2024 15:51:10 +0100 Subject: [PATCH 24/38] Remove is_assignment_stmt flag --- crates/red_knot_python_semantic/src/types/infer.rs | 12 ++++-------- .../red_knot_python_semantic/src/types/unpacker.rs | 9 ++------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 76a8e92b9cf2f..75171b9428d90 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -195,18 +195,14 @@ pub(crate) fn infer_expression_types<'db>( /// type of the variables involved in this unpacking along with any violations that are detected /// during this unpacking. #[salsa::tracked(return_ref)] -fn infer_unpack_types<'db>( - db: &'db dyn Db, - unpack: Unpack<'db>, - is_assignment_stmt: bool, -) -> UnpackResult<'db> { +fn infer_unpack_types<'db>(db: &'db dyn Db, unpack: Unpack<'db>) -> UnpackResult<'db> { let file = unpack.file(db); let _span = tracing::trace_span!("infer_unpack_types", range=?unpack.range(db), file=%file.path(db)) .entered(); let mut unpacker = Unpacker::new(db, unpack.scope(db)); - unpacker.unpack(unpack.target(db), unpack.value(db), is_assignment_stmt); + unpacker.unpack(unpack.target(db), unpack.value(db)); unpacker.finish() } @@ -1901,7 +1897,7 @@ impl<'db> TypeInferenceBuilder<'db> { let mut target_ty = match assignment.target() { TargetKind::Sequence(unpack) => { - let unpacked = infer_unpack_types(self.db(), unpack, true); + let unpacked = infer_unpack_types(self.db(), unpack); // Only copy the diagnostics if this is the first assignment to avoid duplicating the // unpack assignments. if assignment.is_first() { @@ -2167,7 +2163,7 @@ impl<'db> TypeInferenceBuilder<'db> { } else { match for_stmt.target() { TargetKind::Sequence(unpack) => { - let unpacked = infer_unpack_types(self.db(), unpack, false); + let unpacked = infer_unpack_types(self.db(), unpack); if for_stmt.is_first() { self.context.extend(unpacked); } diff --git a/crates/red_knot_python_semantic/src/types/unpacker.rs b/crates/red_knot_python_semantic/src/types/unpacker.rs index 71ebad03b2733..61b1dd63a1502 100644 --- a/crates/red_knot_python_semantic/src/types/unpacker.rs +++ b/crates/red_knot_python_semantic/src/types/unpacker.rs @@ -35,12 +35,7 @@ impl<'db> Unpacker<'db> { self.context.db() } - pub(crate) fn unpack( - &mut self, - target: &ast::Expr, - value: UnpackValue<'db>, - is_assignment_stmt: bool, - ) { + pub(crate) fn unpack(&mut self, target: &ast::Expr, value: UnpackValue<'db>) { let mut value_ty = infer_expression_types(self.db(), value.expression()) .expression_ty(value.scoped_expression_id(self.db(), self.scope)); @@ -52,7 +47,7 @@ impl<'db> Unpacker<'db> { .expression() .node_ref(self.db()) .is_ellipsis_literal_expr(); - if is_assignment_stmt && is_in_stub_file && value_is_ellipsis_literal { + if matches!(value, UnpackValue::Assign(_)) && is_in_stub_file && value_is_ellipsis_literal { value_ty = Type::Unknown; } else { debug_assert!( From 2be530d70b416df802c0fe1b6e7f17b7fe39112d Mon Sep 17 00:00:00 2001 From: Glyphack Date: Tue, 31 Dec 2024 15:53:55 +0100 Subject: [PATCH 25/38] Create is_assign method for UnpackValue --- crates/red_knot_python_semantic/src/types/unpacker.rs | 2 +- crates/red_knot_python_semantic/src/unpack.rs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/src/types/unpacker.rs b/crates/red_knot_python_semantic/src/types/unpacker.rs index 61b1dd63a1502..7f035fe59f296 100644 --- a/crates/red_knot_python_semantic/src/types/unpacker.rs +++ b/crates/red_knot_python_semantic/src/types/unpacker.rs @@ -47,7 +47,7 @@ impl<'db> Unpacker<'db> { .expression() .node_ref(self.db()) .is_ellipsis_literal_expr(); - if matches!(value, UnpackValue::Assign(_)) && is_in_stub_file && value_is_ellipsis_literal { + if value.is_assign() && is_in_stub_file && value_is_ellipsis_literal { value_ty = Type::Unknown; } else { debug_assert!( diff --git a/crates/red_knot_python_semantic/src/unpack.rs b/crates/red_knot_python_semantic/src/unpack.rs index e0b3be92c4b45..470041a4dc9c7 100644 --- a/crates/red_knot_python_semantic/src/unpack.rs +++ b/crates/red_knot_python_semantic/src/unpack.rs @@ -76,6 +76,11 @@ impl<'db> UnpackValue<'db> { matches!(self, UnpackValue::Iterable(_)) } + /// Returns `true` if the value is being assigned to a target. + pub(crate) const fn is_assign(self) -> bool { + matches!(self, UnpackValue::Assign(_)) + } + /// Returns the underlying [`Expression`] that is being unpacked. pub(crate) const fn expression(self) -> Expression<'db> { match self { From fbce5a27cc561a597597ef2c125bf1d04a10e81b Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Sun, 5 Jan 2025 19:31:49 +0100 Subject: [PATCH 26/38] Update crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md Co-authored-by: Alex Waygood --- .../resources/mdtest/stubs/ellipsis.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index b827cb5c05cbe..bb93a6813d35a 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -13,10 +13,10 @@ def f2(x: str = ...) -> None: reveal_type(x) # revealed: str ``` -## Class and module level attributes +## Class and module symbols -The ellipsis literal can be assigned to a class or module attribute, regardless of its type, in a -stub file only. +The ellipsis literal can be assigned to a class or module symbol, regardless of its declared type, +in a stub file only. ```py path=test.pyi y: bytes = ... From 07bc3faa650eaf14ec1fdc53a2b2afebf58b4a88 Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Sun, 5 Jan 2025 19:32:08 +0100 Subject: [PATCH 27/38] Update crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md Co-authored-by: Alex Waygood --- .../red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index bb93a6813d35a..32a46d0d02310 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -62,7 +62,7 @@ b = ... reveal_type(b) # revealed: ellipsis ``` -## Use of ellipsis symbol +## Use of `Ellipsis` symbol There is no special treatment of the builtin name `Ellipsis`, only of `...` literals. From 819293dc75e273a651ff8e9b95d7da172f4bfd48 Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Sun, 5 Jan 2025 19:32:21 +0100 Subject: [PATCH 28/38] Update crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md Co-authored-by: Alex Waygood --- .../red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index 32a46d0d02310..05410f7ebb54c 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -64,7 +64,7 @@ reveal_type(b) # revealed: ellipsis ## Use of `Ellipsis` symbol -There is no special treatment of the builtin name `Ellipsis`, only of `...` literals. +There is no special treatment of the builtin name `Ellipsis` in stubs, only of `...` literals. ```py path=test.pyi # error: 7 [invalid-parameter-default] "Default value of type `ellipsis` is not assignable to annotated parameter type `int`" From e46df8c1bad736d56b026dc8559608f60be07309 Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Sun, 5 Jan 2025 19:32:36 +0100 Subject: [PATCH 29/38] Update crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md Co-authored-by: Alex Waygood --- .../resources/mdtest/stubs/ellipsis.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index 05410f7ebb54c..7868b9cdb31bc 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -40,6 +40,9 @@ reveal_type(y) # revealed: Unknown ## Unpacking ellipsis literal in for loops +Iterating over an ellipsis literal as part of a `for` loop in a stub is invalid, however, and +results in a diagnostic: + ```py path=test.pyi # error: [not-iterable] "Object of type `ellipsis` is not iterable" for a, b in ...: From 1f61e407b87cdd67559301c5da2de7bd22daee93 Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Sun, 5 Jan 2025 19:32:45 +0100 Subject: [PATCH 30/38] Update crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md Co-authored-by: Alex Waygood --- .../resources/mdtest/stubs/ellipsis.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md index 7868b9cdb31bc..fd5c826b98684 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/stubs/ellipsis.md @@ -32,6 +32,9 @@ reveal_type(Foo.y) # revealed: int ## Unpacking ellipsis literal in assignment +No diagnostic is emitted if an ellipsis literal is "unpacked" in a stub file as part of an +assignment statement: + ```py path=test.pyi x, y = ... reveal_type(x) # revealed: Unknown From 29e5b384dba143312589304d3280be6adbac6e40 Mon Sep 17 00:00:00 2001 From: Glyphack Date: Sun, 5 Jan 2025 19:45:21 +0100 Subject: [PATCH 31/38] Rename is_stub to in_stub --- crates/red_knot_python_semantic/src/types/context.rs | 5 +++++ crates/red_knot_python_semantic/src/types/infer.rs | 11 +++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/context.rs b/crates/red_knot_python_semantic/src/types/context.rs index 49ea2af6c7ccf..60ea5aaf8469a 100644 --- a/crates/red_knot_python_semantic/src/types/context.rs +++ b/crates/red_knot_python_semantic/src/types/context.rs @@ -162,6 +162,11 @@ impl<'db> InferContext<'db> { } } + /// Are we currently inferring types in a stub file? + pub(crate) fn in_stub(&self) -> bool { + self.file.is_stub(self.db().upcast()) + } + #[must_use] pub(crate) fn finish(mut self) -> TypeCheckDiagnostics { self.bomb.defuse(); diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 75171b9428d90..739fbc8c5ab21 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -435,11 +435,6 @@ impl<'db> TypeInferenceBuilder<'db> { matches!(self.region, InferenceRegion::Deferred(_)) || self.deferred_state.is_deferred() } - /// Are we currently inferring types in a stub file? - fn is_stub(&self) -> bool { - self.file().is_stub(self.db().upcast()) - } - /// Get the already-inferred type of an expression node. /// /// ## Panics @@ -1179,7 +1174,7 @@ impl<'db> TypeInferenceBuilder<'db> { let inferred_ty = if let Some(default_ty) = default_ty { if default_ty.is_assignable_to(self.db(), declared_ty) { UnionType::from_elements(self.db(), [declared_ty, default_ty]) - } else if self.is_stub() + } else if self.context.in_stub() && default .as_ref() .is_some_and(|d| d.is_ellipsis_literal_expr()) @@ -1908,7 +1903,7 @@ impl<'db> TypeInferenceBuilder<'db> { unpacked.get(name_ast_id).unwrap_or(Type::Unknown) } TargetKind::Name => { - if self.is_stub() && value.is_ellipsis_literal_expr() { + if self.context.in_stub() && value.is_ellipsis_literal_expr() { Type::Unknown } else { value_ty @@ -1980,7 +1975,7 @@ impl<'db> TypeInferenceBuilder<'db> { if let Some(value) = value.as_deref() { let value_ty = self.infer_expression(value); - let value_ty = if self.is_stub() && value.is_ellipsis_literal_expr() { + let value_ty = if self.context.in_stub() && value.is_ellipsis_literal_expr() { annotation_ty } else { value_ty From c4897550f04c7a82bfdbe5c9249e7d9f77ac3089 Mon Sep 17 00:00:00 2001 From: Glyphack Date: Sun, 5 Jan 2025 19:46:18 +0100 Subject: [PATCH 32/38] Remove unnecessary else branch --- crates/red_knot_python_semantic/src/types/unpacker.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/unpacker.rs b/crates/red_knot_python_semantic/src/types/unpacker.rs index 7f035fe59f296..ed91687959711 100644 --- a/crates/red_knot_python_semantic/src/types/unpacker.rs +++ b/crates/red_knot_python_semantic/src/types/unpacker.rs @@ -36,6 +36,10 @@ impl<'db> Unpacker<'db> { } pub(crate) fn unpack(&mut self, target: &ast::Expr, value: UnpackValue<'db>) { + debug_assert!( + matches!(target, ast::Expr::List(_) | ast::Expr::Tuple(_)), + "Unpacking target must be a list or tuple" + ); let mut value_ty = infer_expression_types(self.db(), value.expression()) .expression_ty(value.scoped_expression_id(self.db(), self.scope)); @@ -49,13 +53,7 @@ impl<'db> Unpacker<'db> { .is_ellipsis_literal_expr(); if value.is_assign() && is_in_stub_file && value_is_ellipsis_literal { value_ty = Type::Unknown; - } else { - debug_assert!( - matches!(target, ast::Expr::List(_) | ast::Expr::Tuple(_)), - "Unpacking target must be a list or tuple" - ); } - if value.is_iterable() { // If the value is an iterable, then the type that needs to be unpacked is the iterator // type. From b9eb96625b2806038721214dbfd8226e9b98a397 Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Sun, 5 Jan 2025 19:52:05 +0100 Subject: [PATCH 33/38] Update crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md Co-authored-by: Carl Meyer --- .../resources/mdtest/assignment/annotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md b/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md index 3c9397e905be4..c1fee047feab6 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md +++ b/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md @@ -123,7 +123,7 @@ x = Foo() reveal_type(x) # revealed: Foo ``` -## Annotations in stub files are inferred correctly +## Annotated assignments in stub files are inferred correctly ```pyi path=main.pyi x: int = 1 From e4450185af9a09bba797fe7ada8b298d2b919e9a Mon Sep 17 00:00:00 2001 From: Glyphack Date: Sun, 5 Jan 2025 19:53:40 +0100 Subject: [PATCH 34/38] Add a helper for InferContext::in_stub --- crates/red_knot_python_semantic/src/types/infer.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 739fbc8c5ab21..4fe26fca1d83e 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -435,6 +435,10 @@ impl<'db> TypeInferenceBuilder<'db> { matches!(self.region, InferenceRegion::Deferred(_)) || self.deferred_state.is_deferred() } + fn in_stub(&self) -> bool { + self.context.in_stub() + } + /// Get the already-inferred type of an expression node. /// /// ## Panics @@ -1174,7 +1178,7 @@ impl<'db> TypeInferenceBuilder<'db> { let inferred_ty = if let Some(default_ty) = default_ty { if default_ty.is_assignable_to(self.db(), declared_ty) { UnionType::from_elements(self.db(), [declared_ty, default_ty]) - } else if self.context.in_stub() + } else if self.in_stub() && default .as_ref() .is_some_and(|d| d.is_ellipsis_literal_expr()) @@ -1903,7 +1907,7 @@ impl<'db> TypeInferenceBuilder<'db> { unpacked.get(name_ast_id).unwrap_or(Type::Unknown) } TargetKind::Name => { - if self.context.in_stub() && value.is_ellipsis_literal_expr() { + if self.in_stub() && value.is_ellipsis_literal_expr() { Type::Unknown } else { value_ty @@ -1975,7 +1979,7 @@ impl<'db> TypeInferenceBuilder<'db> { if let Some(value) = value.as_deref() { let value_ty = self.infer_expression(value); - let value_ty = if self.context.in_stub() && value.is_ellipsis_literal_expr() { + let value_ty = if self.in_stub() && value.is_ellipsis_literal_expr() { annotation_ty } else { value_ty From 5d0de3783b1122ca0b83bfd7e2259d1ef1cfce2c Mon Sep 17 00:00:00 2001 From: Shaygan Hooshyari Date: Sun, 5 Jan 2025 19:57:25 +0100 Subject: [PATCH 35/38] Update crates/red_knot_python_semantic/src/types/unpacker.rs Co-authored-by: Carl Meyer --- crates/red_knot_python_semantic/src/types/unpacker.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/src/types/unpacker.rs b/crates/red_knot_python_semantic/src/types/unpacker.rs index ed91687959711..f9c1e31b55c76 100644 --- a/crates/red_knot_python_semantic/src/types/unpacker.rs +++ b/crates/red_knot_python_semantic/src/types/unpacker.rs @@ -38,7 +38,7 @@ impl<'db> Unpacker<'db> { pub(crate) fn unpack(&mut self, target: &ast::Expr, value: UnpackValue<'db>) { debug_assert!( matches!(target, ast::Expr::List(_) | ast::Expr::Tuple(_)), - "Unpacking target must be a list or tuple" + "Unpacking target must be a list or tuple expression" ); let mut value_ty = infer_expression_types(self.db(), value.expression()) .expression_ty(value.scoped_expression_id(self.db(), self.scope)); From 277b30c5f93a9c1761a4ac7a50ae288293cc560c Mon Sep 17 00:00:00 2001 From: Glyphack Date: Sun, 5 Jan 2025 19:58:16 +0100 Subject: [PATCH 36/38] Use InferContext::in_stub --- crates/red_knot_python_semantic/src/types/unpacker.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/unpacker.rs b/crates/red_knot_python_semantic/src/types/unpacker.rs index f9c1e31b55c76..3685109dd3707 100644 --- a/crates/red_knot_python_semantic/src/types/unpacker.rs +++ b/crates/red_knot_python_semantic/src/types/unpacker.rs @@ -43,10 +43,7 @@ impl<'db> Unpacker<'db> { let mut value_ty = infer_expression_types(self.db(), value.expression()) .expression_ty(value.scoped_expression_id(self.db(), self.scope)); - let is_in_stub_file = value - .expression() - .file(self.db()) - .is_stub(self.db().upcast()); + let is_in_stub_file = self.context.in_stub(); let value_is_ellipsis_literal = value .expression() .node_ref(self.db()) From eaca9c90dfe65983324aaebb132a1efea2bb143d Mon Sep 17 00:00:00 2001 From: Glyphack Date: Sun, 5 Jan 2025 20:00:19 +0100 Subject: [PATCH 37/38] Revert unnecessary change --- crates/red_knot_python_semantic/src/types/unpacker.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/red_knot_python_semantic/src/types/unpacker.rs b/crates/red_knot_python_semantic/src/types/unpacker.rs index 3685109dd3707..d4d042176a5bf 100644 --- a/crates/red_knot_python_semantic/src/types/unpacker.rs +++ b/crates/red_knot_python_semantic/src/types/unpacker.rs @@ -35,11 +35,13 @@ impl<'db> Unpacker<'db> { self.context.db() } + /// Unpack the value to the target expression. pub(crate) fn unpack(&mut self, target: &ast::Expr, value: UnpackValue<'db>) { debug_assert!( matches!(target, ast::Expr::List(_) | ast::Expr::Tuple(_)), "Unpacking target must be a list or tuple expression" ); + let mut value_ty = infer_expression_types(self.db(), value.expression()) .expression_ty(value.scoped_expression_id(self.db(), self.scope)); From d8315da1ede8240adf9ca8867261807b35bdc6c5 Mon Sep 17 00:00:00 2001 From: Glyphack Date: Sun, 5 Jan 2025 20:02:16 +0100 Subject: [PATCH 38/38] Refactor condition for ellipsis literal Co-Authored-by: Alex Waygood --- .../red_knot_python_semantic/src/types/unpacker.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/unpacker.rs b/crates/red_knot_python_semantic/src/types/unpacker.rs index d4d042176a5bf..aaafe18e6aace 100644 --- a/crates/red_knot_python_semantic/src/types/unpacker.rs +++ b/crates/red_knot_python_semantic/src/types/unpacker.rs @@ -45,12 +45,13 @@ impl<'db> Unpacker<'db> { let mut value_ty = infer_expression_types(self.db(), value.expression()) .expression_ty(value.scoped_expression_id(self.db(), self.scope)); - let is_in_stub_file = self.context.in_stub(); - let value_is_ellipsis_literal = value - .expression() - .node_ref(self.db()) - .is_ellipsis_literal_expr(); - if value.is_assign() && is_in_stub_file && value_is_ellipsis_literal { + if value.is_assign() + && self.context.in_stub() + && value + .expression() + .node_ref(self.db()) + .is_ellipsis_literal_expr() + { value_ty = Type::Unknown; } if value.is_iterable() {