From bc9e11697e8119c1f5a5f5ff29315f18c0a067fa Mon Sep 17 00:00:00 2001 From: InSyncWithFoo Date: Thu, 28 Nov 2024 00:04:36 +0000 Subject: [PATCH 1/6] [red-knot] Deeper understanding of `LiteralString` --- .../mdtest/annotations/literal_string.md | 209 ++++++++++++++++++ crates/red_knot_python_semantic/src/types.rs | 15 +- .../src/types/infer.rs | 11 + .../red_knot_python_semantic/src/types/mro.rs | 1 + .../src/types/narrow.rs | 13 ++ 5 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md new file mode 100644 index 0000000000000..f11686c7c7308 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md @@ -0,0 +1,209 @@ +# `LiteralString` + +`LiteralString` signifies a strings that is either +defined directly within the source code or is made up of such components. + +Part of the testcases defined here were adapted from [the specification's examples][1]. + +[1]: https://typing.readthedocs.io/en/latest/spec/literal.html#literalstring + +## Usages + +### Valid places + +It can be used anywhere a type is accepted: + +```py +from typing import ( + Annotated, + ClassVar, + Final, + Literal, + LiteralString, + NotRequired, + Protocol, + ReadOnly, + Required, + TypedDict, + TypeAlias, + # TODO: Blocking on `sys.version_info` support. + # See `issubclass.md`, section "Handling of `None`" + TypeAliasType, # error: [possibly-unbound-import] + TypeVar, +) + +Old: TypeAlias = LiteralString +type New = LiteralString +Backported = TypeAliasType("Backported", LiteralString) + +T1 = TypeVar("T1", bound=LiteralString) +T2 = TypeVar("T2", bound=Old) +T3 = TypeVar("T3", bound=New) +T4 = TypeVar("T4", bound=Backported) + +variable_annotation_1: LiteralString +variable_annotation_2: Old +variable_annotation_3: New +variable_annotation_4: Backported + +type_argument_1: list[LiteralString] +type_argument_2: dict[Old, New] +type_argument_3: set[Backported] + +type TA1[LS: LiteralString] = Old +type TA2[LS: Old] = New +type TA3[LS: New] = Backported +type TA4[LS: Backported] = LiteralString + +def my_function(literal_string: LiteralString, *args: Old, **kwargs: New) -> Backported: ... + +class Foo: + my_attribute: LiteralString + class_var: ClassVar[Old] = "foo" + final: Final[New] = "bar" + annotated_class_var: Annotated[ + ClassVar[Backported], + Literal[LiteralString] # Second arguments and later must be ignored. + ] = "foobar" + +# TODO: Support new-style generic classes +# error: [invalid-base] +class PEP695[L: LiteralString](Protocol): + def f[S: Old](self: L | S | New) -> Backported: ... + # ^^^^^^^^^^^ This is valid, as the class is a protocol. + +class GenericParameter(PEP695[LiteralString]): + ... + +# TODO: Support TypedDict +class TD(TypedDict): # error: [invalid-base] + normal: LiteralString + readonly: ReadOnly[Old] + required: Required[New] + not_required: NotRequired[Backported] +``` + +### Within `Literal` + +`LiteralString` cannot be used within `Literal`: + +```py +from typing import Literal, LiteralString + +bad_union: Literal["hello", LiteralString] # error: [invalid-literal-parameter] +bad_nesting: Literal[LiteralString] # error: [invalid-literal-parameter] +``` + +### Parametrized + +`LiteralString` cannot be parametrized. + +```py +# TODO: See above. +# error: [possibly-unbound-import] +from typing import LiteralString, TypeAlias, TypeAliasType + +Old: TypeAlias = LiteralString +type New = LiteralString +Backported = TypeAliasType("Backported", LiteralString) + +a: LiteralString[str] # error: [invalid-type-parameter] +b: LiteralString["foo"] # error: [invalid-type-parameter] + +c: Old[str] # error: [invalid-type-parameter] +d: Old["foo"] # error: [invalid-type-parameter] + +# TODO: Emit invalid-type-parameter for the following +e: New[str] +f: New["int"] + +g: Backported[str] +h: Backported["int"] + +# fine: TypeAliasType instances are subscriptable. +# These are not required to have a meaning outside annotation contexts. +New[str] +New["int"] +Backported[str] +Backported["int"] +``` + +### As a base class + +Subclassing `LiteralString` leads to a runtime error. + +```py +from typing import LiteralString + +class C(LiteralString): ... # error: [invalid-base] +``` + +## Inference + +### Common operations + +```py +foo: LiteralString = "foo" +reveal_type(foo) # revealed: Literal["foo"] + +bar: LiteralString = "bar" +reveal_type(foo + bar) # revealed: Literal["foobar"] + +baz: LiteralString = "baz" +baz += foo +reveal_type(baz) # revealed: Literal["bazfoo"] + +qux = (foo, bar) +reveal_type(qux) # revealed: tuple[Literal["foo"], Literal["bar"]] + +# TODO: Infer "LiteralString" +reveal_type(foo.join(qux)) # revealed: @Todo(call type) + +template: LiteralString = "{}, {}" +reveal_type(template) # revealed: Literal["{}, {}"] +# TODO: Infer "foo, bar" +reveal_type(template.format(foo, bar)) # revealed: @Todo(call type) +``` + +### Compatibility + +`Literal["", ...]` is compatible with `LiteralString`, +`LiteralString` is compatible with `str`, but not vice versa. + +```py +foo_1: Literal["foo"] = "foo" +bar_1: LiteralString = foo_1 # fine + +if bool(): + foo_2 = "foo" +else: + foo_2 = "bar" +reveal_type(foo_2) # revealed: Literal["foo", "bar"] +bar_2: LiteralString = foo_2 # fine + +foo_3: LiteralString = "foo" * 1_000_000_000 +bar_3: str = foo_2 # fine + +baz_1: str = str() +qux_1: LiteralString = baz_1 # error: [invalid-assignment] + +baz_2: LiteralString = "baz" * 1_000_000_000 +qux_2: Literal["qux"] = baz_2 # error: [invalid-assignment] + +if bool(): + baz_3 = "foo" +else: + baz_3 = 1 +reveal_type(baz_3) # revealed: Literal["foo"] | Literal[1] +qux_3: LiteralString = baz_3 # error: [invalid-assignment] +``` + +### Narrowing + +```py +lorem: LiteralString = "lorem" * 1_000_000_000 +reveal_type(lorem) # revealed: LiteralString + +if lorem == "ipsum": + reveal_type(lorem) # revealed: Literal["ipsum"] +``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 64bfd3ec79660..942835b5d95c8 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -629,6 +629,11 @@ impl<'db> Type<'db> { Type::StringLiteral(_) | Type::LiteralString, Type::Instance(InstanceType { class }), ) if class.is_known(db, KnownClass::Str) => true, + ( + Type::Instance(InstanceType { class }), + Type::StringLiteral(_) | Type::LiteralString, + ) if class.is_known(db, KnownClass::Str) => false, + (Type::LiteralString, Type::StringLiteral(_)) => false, (Type::BytesLiteral(_), Type::Instance(InstanceType { class })) if class.is_known(db, KnownClass::Bytes) => { @@ -1403,7 +1408,7 @@ impl<'db> Type<'db> { // `Any` is callable, and its return type is also `Any`. Type::Any => CallOutcome::callable(Type::Any), - Type::Todo(_) => CallOutcome::callable(todo_type!()), + Type::Todo(_) => CallOutcome::callable(todo_type!("call type")), Type::Unknown => CallOutcome::callable(Type::Unknown), @@ -1556,6 +1561,7 @@ impl<'db> Type<'db> { Type::KnownInstance(KnownInstanceType::Never | KnownInstanceType::NoReturn) => { Type::Never } + Type::KnownInstance(KnownInstanceType::LiteralString) => Type::LiteralString, _ => todo_type!(), } } @@ -1888,6 +1894,8 @@ impl<'db> KnownClass { pub enum KnownInstanceType<'db> { /// The symbol `typing.Literal` (which can also be found as `typing_extensions.Literal`) Literal, + /// The symbol `typing.LiteralString` (which can also be found as `typing_extensions.LiteralString`) + LiteralString, /// The symbol `typing.Optional` (which can also be found as `typing_extensions.Optional`) Optional, /// The symbol `typing.Union` (which can also be found as `typing_extensions.Union`) @@ -1907,6 +1915,7 @@ impl<'db> KnownInstanceType<'db> { pub const fn as_str(self) -> &'static str { match self { Self::Literal => "Literal", + Self::LiteralString => "LiteralString", Self::Optional => "Optional", Self::Union => "Union", Self::TypeVar(_) => "TypeVar", @@ -1920,6 +1929,7 @@ impl<'db> KnownInstanceType<'db> { pub const fn bool(self) -> Truthiness { match self { Self::Literal + | Self::LiteralString | Self::Optional | Self::TypeVar(_) | Self::Union @@ -1933,6 +1943,7 @@ impl<'db> KnownInstanceType<'db> { pub fn repr(self, db: &'db dyn Db) -> &'db str { match self { Self::Literal => "typing.Literal", + Self::LiteralString => "typing.LiteralString", Self::Optional => "typing.Optional", Self::Union => "typing.Union", Self::NoReturn => "typing.NoReturn", @@ -1946,6 +1957,7 @@ impl<'db> KnownInstanceType<'db> { pub const fn class(self) -> KnownClass { match self { Self::Literal => KnownClass::SpecialForm, + Self::LiteralString => KnownClass::SpecialForm, Self::Optional => KnownClass::SpecialForm, Self::Union => KnownClass::SpecialForm, Self::NoReturn => KnownClass::SpecialForm, @@ -1970,6 +1982,7 @@ impl<'db> KnownInstanceType<'db> { } match (module.name().as_str(), instance_name) { ("typing" | "typing_extensions", "Literal") => Some(Self::Literal), + ("typing" | "typing_extensions", "LiteralString") => Some(Self::LiteralString), ("typing" | "typing_extensions", "Optional") => Some(Self::Optional), ("typing" | "typing_extensions", "Union") => Some(Self::Union), ("typing" | "typing_extensions", "NoReturn") => Some(Self::NoReturn), diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 7224ac0c9daba..a27603d0415bf 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -4642,6 +4642,17 @@ impl<'db> TypeInferenceBuilder<'db> { ); Type::Unknown } + KnownInstanceType::LiteralString => { + self.diagnostics.add( + subscript.into(), + "invalid-type-parameter", + format_args!( + "Type `{}` expected no type parameter. Did you mean to use `Literal[...]` instead?", + known_instance.repr(self.db) + ), + ); + Type::Unknown + } } } diff --git a/crates/red_knot_python_semantic/src/types/mro.rs b/crates/red_knot_python_semantic/src/types/mro.rs index e576d7c634647..a91960655e32a 100644 --- a/crates/red_knot_python_semantic/src/types/mro.rs +++ b/crates/red_knot_python_semantic/src/types/mro.rs @@ -374,6 +374,7 @@ impl<'db> ClassBase<'db> { KnownInstanceType::TypeVar(_) | KnownInstanceType::TypeAliasType(_) | KnownInstanceType::Literal + | KnownInstanceType::LiteralString | KnownInstanceType::Union | KnownInstanceType::NoReturn | KnownInstanceType::Never diff --git a/crates/red_knot_python_semantic/src/types/narrow.rs b/crates/red_knot_python_semantic/src/types/narrow.rs index 7ce84cb7fb553..775cf80f81055 100644 --- a/crates/red_knot_python_semantic/src/types/narrow.rs +++ b/crates/red_knot_python_semantic/src/types/narrow.rs @@ -291,8 +291,15 @@ impl<'db> NarrowingConstraintsBuilder<'db> { .chain(comparators) .tuple_windows::<(&ruff_python_ast::Expr, &ruff_python_ast::Expr)>(); let mut constraints = NarrowingConstraints::default(); + + let mut last_rhs_ty: Option = None; + for (op, (left, right)) in std::iter::zip(&**ops, comparator_tuples) { + let lhs_ty = last_rhs_ty.unwrap_or_else(|| { + inference.expression_ty(left.scoped_expression_id(self.db, scope)) + }); let rhs_ty = inference.expression_ty(right.scoped_expression_id(self.db, scope)); + last_rhs_ty = Some(rhs_ty); match left { ast::Expr::Name(ast::ExprName { @@ -327,6 +334,12 @@ impl<'db> NarrowingConstraintsBuilder<'db> { constraints.insert(symbol, ty); } } + ast::CmpOp::Eq if lhs_ty.is_literal_string() => { + let ty = IntersectionBuilder::new(self.db) + .add_positive(rhs_ty) + .build(); + constraints.insert(symbol, ty); + } _ => { // TODO other comparison types } From d5df02c9a1197f1463c4e61ec555b4776ac35e91 Mon Sep 17 00:00:00 2001 From: InSyncWithFoo Date: Thu, 28 Nov 2024 00:15:49 +0000 Subject: [PATCH 2/6] Formatting --- .../mdtest/annotations/literal_string.md | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md index f11686c7c7308..398c34a22caa9 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md @@ -1,11 +1,9 @@ # `LiteralString` -`LiteralString` signifies a strings that is either -defined directly within the source code or is made up of such components. +`LiteralString` signifies a strings that is either defined directly within the source code or is +made up of such components. -Part of the testcases defined here were adapted from [the specification's examples][1]. - -[1]: https://typing.readthedocs.io/en/latest/spec/literal.html#literalstring +Parts of the testcases defined here were adapted from [the specification's examples][1]. ## Usages @@ -62,8 +60,7 @@ class Foo: class_var: ClassVar[Old] = "foo" final: Final[New] = "bar" annotated_class_var: Annotated[ - ClassVar[Backported], - Literal[LiteralString] # Second arguments and later must be ignored. + ClassVar[Backported], Literal[LiteralString] # Second arguments and later must be ignored. ] = "foobar" # TODO: Support new-style generic classes @@ -72,8 +69,7 @@ class PEP695[L: LiteralString](Protocol): def f[S: Old](self: L | S | New) -> Backported: ... # ^^^^^^^^^^^ This is valid, as the class is a protocol. -class GenericParameter(PEP695[LiteralString]): - ... +class GenericParameter(PEP695[LiteralString]): ... # TODO: Support TypedDict class TD(TypedDict): # error: [invalid-base] @@ -167,8 +163,8 @@ reveal_type(template.format(foo, bar)) # revealed: @Todo(call type) ### Compatibility -`Literal["", ...]` is compatible with `LiteralString`, -`LiteralString` is compatible with `str`, but not vice versa. +`Literal["", ...]` is compatible with `LiteralString`, `LiteralString` is compatible with `str`, but +not vice versa. ```py foo_1: Literal["foo"] = "foo" @@ -207,3 +203,5 @@ reveal_type(lorem) # revealed: LiteralString if lorem == "ipsum": reveal_type(lorem) # revealed: Literal["ipsum"] ``` + +[1]: https://typing.readthedocs.io/en/latest/spec/literal.html#literalstring From 85b7a26a534cf6bc58e730f0e22ab657d87a998e Mon Sep 17 00:00:00 2001 From: InSyncWithFoo Date: Thu, 28 Nov 2024 13:14:47 +0000 Subject: [PATCH 3/6] Per review --- .../mdtest/annotations/literal_string.md | 89 +++---------------- crates/red_knot_python_semantic/src/types.rs | 7 +- .../src/types/narrow.rs | 5 +- 3 files changed, 16 insertions(+), 85 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md index 398c34a22caa9..31e00a5366f24 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md @@ -1,6 +1,6 @@ # `LiteralString` -`LiteralString` signifies a strings that is either defined directly within the source code or is +`LiteralString` represents a string that is either defined directly within the source code or is made up of such components. Parts of the testcases defined here were adapted from [the specification's examples][1]. @@ -23,60 +23,19 @@ from typing import ( ReadOnly, Required, TypedDict, - TypeAlias, - # TODO: Blocking on `sys.version_info` support. - # See `issubclass.md`, section "Handling of `None`" - TypeAliasType, # error: [possibly-unbound-import] TypeVar, ) -Old: TypeAlias = LiteralString -type New = LiteralString -Backported = TypeAliasType("Backported", LiteralString) +TA: TypeAlias = LiteralString T1 = TypeVar("T1", bound=LiteralString) -T2 = TypeVar("T2", bound=Old) -T3 = TypeVar("T3", bound=New) -T4 = TypeVar("T4", bound=Backported) -variable_annotation_1: LiteralString -variable_annotation_2: Old -variable_annotation_3: New -variable_annotation_4: Backported - -type_argument_1: list[LiteralString] -type_argument_2: dict[Old, New] -type_argument_3: set[Backported] - -type TA1[LS: LiteralString] = Old -type TA2[LS: Old] = New -type TA3[LS: New] = Backported -type TA4[LS: Backported] = LiteralString - -def my_function(literal_string: LiteralString, *args: Old, **kwargs: New) -> Backported: ... +variable_annotation: LiteralString class Foo: - my_attribute: LiteralString - class_var: ClassVar[Old] = "foo" - final: Final[New] = "bar" - annotated_class_var: Annotated[ - ClassVar[Backported], Literal[LiteralString] # Second arguments and later must be ignored. - ] = "foobar" - -# TODO: Support new-style generic classes -# error: [invalid-base] -class PEP695[L: LiteralString](Protocol): - def f[S: Old](self: L | S | New) -> Backported: ... - # ^^^^^^^^^^^ This is valid, as the class is a protocol. - -class GenericParameter(PEP695[LiteralString]): ... - -# TODO: Support TypedDict -class TD(TypedDict): # error: [invalid-base] - normal: LiteralString - readonly: ReadOnly[Old] - required: Required[New] - not_required: NotRequired[Backported] + annotated: Annotated[ + int, Literal[LiteralString] # fine: Second arguments and later must be ignored. + ] ``` ### Within `Literal` @@ -95,33 +54,10 @@ bad_nesting: Literal[LiteralString] # error: [invalid-literal-parameter] `LiteralString` cannot be parametrized. ```py -# TODO: See above. -# error: [possibly-unbound-import] -from typing import LiteralString, TypeAlias, TypeAliasType - -Old: TypeAlias = LiteralString -type New = LiteralString -Backported = TypeAliasType("Backported", LiteralString) +from typing import LiteralString a: LiteralString[str] # error: [invalid-type-parameter] b: LiteralString["foo"] # error: [invalid-type-parameter] - -c: Old[str] # error: [invalid-type-parameter] -d: Old["foo"] # error: [invalid-type-parameter] - -# TODO: Emit invalid-type-parameter for the following -e: New[str] -f: New["int"] - -g: Backported[str] -h: Backported["int"] - -# fine: TypeAliasType instances are subscriptable. -# These are not required to have a meaning outside annotation contexts. -New[str] -New["int"] -Backported[str] -Backported["int"] ``` ### As a base class @@ -153,17 +89,17 @@ qux = (foo, bar) reveal_type(qux) # revealed: tuple[Literal["foo"], Literal["bar"]] # TODO: Infer "LiteralString" -reveal_type(foo.join(qux)) # revealed: @Todo(call type) +reveal_type(foo.join(qux)) # revealed: @Todo(call todo) template: LiteralString = "{}, {}" reveal_type(template) # revealed: Literal["{}, {}"] # TODO: Infer "foo, bar" -reveal_type(template.format(foo, bar)) # revealed: @Todo(call type) +reveal_type(template.format(foo, bar)) # revealed: @Todo(call todo) ``` -### Compatibility +### Assignability -`Literal["", ...]` is compatible with `LiteralString`, `LiteralString` is compatible with `str`, but +`Literal[""]` is assignable to `LiteralString`, and `LiteralString` is assignable to `str`, but not vice versa. ```py @@ -202,6 +138,9 @@ reveal_type(lorem) # revealed: LiteralString if lorem == "ipsum": reveal_type(lorem) # revealed: Literal["ipsum"] + +if "" < lorem == "ipsum": + reveal_type(lorem) # revealed: Literal["ipsum"] ``` [1]: https://typing.readthedocs.io/en/latest/spec/literal.html#literalstring diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 942835b5d95c8..d1b561ccbd7ef 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -629,11 +629,6 @@ impl<'db> Type<'db> { Type::StringLiteral(_) | Type::LiteralString, Type::Instance(InstanceType { class }), ) if class.is_known(db, KnownClass::Str) => true, - ( - Type::Instance(InstanceType { class }), - Type::StringLiteral(_) | Type::LiteralString, - ) if class.is_known(db, KnownClass::Str) => false, - (Type::LiteralString, Type::StringLiteral(_)) => false, (Type::BytesLiteral(_), Type::Instance(InstanceType { class })) if class.is_known(db, KnownClass::Bytes) => { @@ -1408,7 +1403,7 @@ impl<'db> Type<'db> { // `Any` is callable, and its return type is also `Any`. Type::Any => CallOutcome::callable(Type::Any), - Type::Todo(_) => CallOutcome::callable(todo_type!("call type")), + Type::Todo(_) => CallOutcome::callable(todo_type!("call todo")), Type::Unknown => CallOutcome::callable(Type::Unknown), diff --git a/crates/red_knot_python_semantic/src/types/narrow.rs b/crates/red_knot_python_semantic/src/types/narrow.rs index 775cf80f81055..c694a16038856 100644 --- a/crates/red_knot_python_semantic/src/types/narrow.rs +++ b/crates/red_knot_python_semantic/src/types/narrow.rs @@ -335,10 +335,7 @@ impl<'db> NarrowingConstraintsBuilder<'db> { } } ast::CmpOp::Eq if lhs_ty.is_literal_string() => { - let ty = IntersectionBuilder::new(self.db) - .add_positive(rhs_ty) - .build(); - constraints.insert(symbol, ty); + constraints.insert(symbol, rhs_ty); } _ => { // TODO other comparison types From 8af2f6996ec9a72723e65462aaf95e5193c40cfc Mon Sep 17 00:00:00 2001 From: InSyncWithFoo Date: Thu, 28 Nov 2024 13:20:50 +0000 Subject: [PATCH 4/6] Per review --- .../mdtest/annotations/literal_string.md | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md index 31e00a5366f24..736e965f3ab57 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md @@ -33,9 +33,8 @@ T1 = TypeVar("T1", bound=LiteralString) variable_annotation: LiteralString class Foo: - annotated: Annotated[ - int, Literal[LiteralString] # fine: Second arguments and later must be ignored. - ] + # fine: Second arguments and later must be ignored. + annotated: Annotated[int, Literal[LiteralString]] ``` ### Within `Literal` @@ -99,17 +98,17 @@ reveal_type(template.format(foo, bar)) # revealed: @Todo(call todo) ### Assignability -`Literal[""]` is assignable to `LiteralString`, and `LiteralString` is assignable to `str`, but -not vice versa. +`Literal[""]` is assignable to `LiteralString`, and `LiteralString` is assignable to `str`, but not +vice versa. ```py +def coinflip() -> bool: + return True + foo_1: Literal["foo"] = "foo" bar_1: LiteralString = foo_1 # fine -if bool(): - foo_2 = "foo" -else: - foo_2 = "bar" +foo_2 = "foo" if coinflip() else "bar" reveal_type(foo_2) # revealed: Literal["foo", "bar"] bar_2: LiteralString = foo_2 # fine @@ -122,10 +121,7 @@ qux_1: LiteralString = baz_1 # error: [invalid-assignment] baz_2: LiteralString = "baz" * 1_000_000_000 qux_2: Literal["qux"] = baz_2 # error: [invalid-assignment] -if bool(): - baz_3 = "foo" -else: - baz_3 = 1 +baz_3 = "foo" if coinflip() else 1 reveal_type(baz_3) # revealed: Literal["foo"] | Literal[1] qux_3: LiteralString = baz_3 # error: [invalid-assignment] ``` From 460db1fe3e375909c044bf9c3c82cb11d6178fa2 Mon Sep 17 00:00:00 2001 From: InSyncWithFoo Date: Thu, 28 Nov 2024 13:38:42 +0000 Subject: [PATCH 5/6] Remove TODO note for `.format()` --- .../resources/mdtest/annotations/literal_string.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md index 736e965f3ab57..83c1820246e20 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md @@ -92,7 +92,7 @@ reveal_type(foo.join(qux)) # revealed: @Todo(call todo) template: LiteralString = "{}, {}" reveal_type(template) # revealed: Literal["{}, {}"] -# TODO: Infer "foo, bar" +# TODO: Infer `LiteralString` reveal_type(template.format(foo, bar)) # revealed: @Todo(call todo) ``` @@ -130,11 +130,14 @@ qux_3: LiteralString = baz_3 # error: [invalid-assignment] ```py lorem: LiteralString = "lorem" * 1_000_000_000 + reveal_type(lorem) # revealed: LiteralString if lorem == "ipsum": reveal_type(lorem) # revealed: Literal["ipsum"] +reveal_type(lorem) # revealed: LiteralString + if "" < lorem == "ipsum": reveal_type(lorem) # revealed: Literal["ipsum"] ``` From 53bb67c72d784626c3e80cab2e6cd37396d88ff2 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Mon, 2 Dec 2024 20:27:29 -0700 Subject: [PATCH 6/6] Update crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md --- .../mdtest/annotations/literal_string.md | 29 ++++--------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md index 83c1820246e20..518474f9d507c 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md @@ -12,29 +12,12 @@ Parts of the testcases defined here were adapted from [the specification's examp It can be used anywhere a type is accepted: ```py -from typing import ( - Annotated, - ClassVar, - Final, - Literal, - LiteralString, - NotRequired, - Protocol, - ReadOnly, - Required, - TypedDict, - TypeVar, -) - -TA: TypeAlias = LiteralString - -T1 = TypeVar("T1", bound=LiteralString) - -variable_annotation: LiteralString - -class Foo: - # fine: Second arguments and later must be ignored. - annotated: Annotated[int, Literal[LiteralString]] +from typing import LiteralString + +x: LiteralString + +def f(): + reveal_type(x) # revealed: LiteralString ``` ### Within `Literal`