From e3d4f710884aff8026ed545e0f5baeebfb259a7b Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Tue, 4 Nov 2025 22:33:50 +0900 Subject: [PATCH 1/8] fix --- pyrefly/lib/solver/solver.rs | 13 ++++++------- pyrefly/lib/test/generic_basic.rs | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/pyrefly/lib/solver/solver.rs b/pyrefly/lib/solver/solver.rs index 7e6e1806a2..9fe885e21b 100644 --- a/pyrefly/lib/solver/solver.rs +++ b/pyrefly/lib/solver/solver.rs @@ -1245,17 +1245,16 @@ impl<'a, Ans: LookupAnswer> Subset<'a, Ans> { let name = q.name.clone(); let bound = q.restriction().as_type(self.type_order.stdlib()); drop(v2_ref); - variables.update(*v2, Variable::Answer(t1_p.clone())); + variables.update(*v2, Variable::Answer(t1.clone())); drop(variables); - if let Err(err_p) = self.is_subset_eq(&t1_p, &bound) { - // If the promoted type fails, try again with the original type, in case the bound itself is literal. - // This could be more optimized, but errors are rare, so this code path should not be hot. + if self.is_subset_eq(t1, &bound).is_err() { + // Fall back to the promoted type if the literal version violates the bound. self.solver .variables .lock() - .update(*v2, Variable::Answer(t1.clone())); - if self.is_subset_eq(t1, &bound).is_err() { - // If the original type is also an error, use the promoted type. + .update(*v2, Variable::Answer(t1_p.clone())); + if let Err(err_p) = self.is_subset_eq(&t1_p, &bound) { + // If the promoted type also violates the bound, record the error. self.solver .variables .lock() diff --git a/pyrefly/lib/test/generic_basic.rs b/pyrefly/lib/test/generic_basic.rs index 42f8111529..e594dcc616 100644 --- a/pyrefly/lib/test/generic_basic.rs +++ b/pyrefly/lib/test/generic_basic.rs @@ -126,6 +126,22 @@ assert_type(f(D), D) "#, ); +testcase!( + bug = "regression test for https://github.com/facebook/pyrefly/issues/1138", + test_preserve_literal_through_generic_call, + r#" +from typing import Literal, assert_type + +def f[T](t: T) -> T: + return t + +x = 1 +assert_type(x, Literal[1]) +assert_type(f(x), Literal[1]) +assert_type(f(1), Literal[1]) +"#, +); + testcase!( test_untype_with_missing_targs_annotation, TestEnv::new().enable_implicit_any_error(), From 12a3da433cf76a3b14dc7962ec99b9aa20240e38 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Tue, 4 Nov 2025 23:21:09 +0900 Subject: [PATCH 2/8] fix some --- pyrefly/lib/test/attributes.rs | 8 +++---- pyrefly/lib/test/callable.rs | 34 +++++++++++++++--------------- pyrefly/lib/test/calls.rs | 12 +++++------ pyrefly/lib/test/decorators.rs | 4 ++-- pyrefly/lib/test/generic_legacy.rs | 4 ++-- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/pyrefly/lib/test/attributes.rs b/pyrefly/lib/test/attributes.rs index eea1545bdc..6ecd9b0322 100644 --- a/pyrefly/lib/test/attributes.rs +++ b/pyrefly/lib/test/attributes.rs @@ -1365,11 +1365,11 @@ assert_type(A.f(A[int]()), int) testcase!( test_access_generic_method_using_class_param_on_class, r#" -from typing import assert_type, reveal_type, Any +from typing import Literal, assert_type, reveal_type, Any class A[T]: def f[S](self, x: S) -> tuple[S, T]: ... reveal_type(A.f) # E: revealed type: [T, S](self: A[T], x: S) -> tuple[S, T] -assert_type(A.f(A[int](), ""), tuple[str, int]) # E: assert_type(tuple[str, Any], tuple[str, int]) +assert_type(A.f(A[int](), ""), tuple[str, int]) # E: assert_type(tuple[Literal[''], Any], tuple[str, int]) "#, ); @@ -1391,7 +1391,7 @@ assert_type(A.f(A[int]()), int) testcase!( test_access_overloaded_staticmethod_using_class_param_on_class, r#" -from typing import assert_type, reveal_type, overload, Any +from typing import Literal, assert_type, reveal_type, overload, Any class A[T]: @overload @staticmethod @@ -1403,7 +1403,7 @@ class A[T]: def f(x = None) -> Any: ... reveal_type(A.f) # E: revealed type: Overload[(x: None = ...) -> None, [T](x: T) -> T] assert_type(A.f(), None) -assert_type(A.f(0), int) +assert_type(A.f(0), Literal[0]) "#, ); diff --git a/pyrefly/lib/test/callable.rs b/pyrefly/lib/test/callable.rs index 4026604a28..328a15f2f7 100644 --- a/pyrefly/lib/test/callable.rs +++ b/pyrefly/lib/test/callable.rs @@ -456,7 +456,7 @@ test(1, 2, 3, *[4]) # OK testcase!( test_splat_unpacked_args, r#" -from typing import assert_type +from typing import Literal, assert_type def test1(*args: *tuple[int, int, int]): ... test1(*(1, 2, 3)) # OK @@ -464,11 +464,11 @@ test1(*(1, 2)) # E: Unpacked argument `tuple[Literal[1], Literal[2]]` is not ass test1(*(1, 2, 3, 4)) # E: Unpacked argument `tuple[Literal[1], Literal[2], Literal[3], Literal[4]]` is not assignable to parameter `*args` with type `tuple[int, int, int]` in function `test1` def test2[*T](*args: *tuple[int, *T, int]) -> tuple[*T]: ... -assert_type(test2(*(1, 2, 3)), tuple[int]) +assert_type(test2(*(1, 2, 3)), tuple[Literal[2]]) assert_type(test2(*(1, 2)), tuple[()]) -assert_type(test2(*(1, 2, 3, 4)), tuple[int, int]) -assert_type(test2(1, 2, *(3, 4), 5), tuple[int, int, int]) -assert_type(test2(1, *(2, 3), *("4", 5)), tuple[int, int, str]) +assert_type(test2(*(1, 2, 3, 4)), tuple[Literal[2], Literal[3]]) +assert_type(test2(1, 2, *(3, 4), 5), tuple[Literal[2], Literal[3], Literal[4]]) +assert_type(test2(1, *(2, 3), *("4", 5)), tuple[Literal[2], Literal[3], Literal['4']]) assert_type(test2(1, *[2, 3], 4), tuple[int, ...]) test2(1, *(2, 3), *(4, "5")) # E: Unpacked argument `tuple[Literal[1], Literal[2], Literal[3], Literal[4], Literal['5']]` is not assignable to parameter `*args` with type `tuple[int, *@_, int]` in function `test2` "#, @@ -933,13 +933,13 @@ c3: Callable[[C], C] = f # OK testcase!( test_return_generic_callable, r#" -from typing import assert_type, Callable +from typing import Literal, assert_type, Callable def f[T]() -> Callable[[T], T]: return lambda x: x g = f() -assert_type(g(0), int) -assert_type(g(""), str) +assert_type(g(0), Literal[0]) +assert_type(g(""), Literal['']) @f() def h(x: int) -> int: @@ -951,18 +951,18 @@ assert_type(h(0), int) testcase!( test_generic_callable_union, r#" -from typing import assert_type, Callable +from typing import Literal, assert_type, Callable def f[T]() -> Callable[[T], T] | Callable[[T], list[T]]: ... g = f() -assert_type(g(0), int | list[int]) -assert_type(g(""), str | list[str]) +assert_type(g(0), Literal[0] | list[Literal[0]]) +assert_type(g(""), Literal[''] | list[Literal['']]) "#, ); testcase!( test_callable_returns_callable_returns_callable, r#" -from typing import assert_type, Callable +from typing import Literal, assert_type, Callable def f[T]() -> Callable[[], Callable[[T], T]]: def f(): @@ -970,8 +970,8 @@ def f[T]() -> Callable[[], Callable[[T], T]]: return f g = f()() -assert_type(g(0), int) -assert_type(g(""), str) +assert_type(g(0), Literal[0]) +assert_type(g(""), Literal['']) "#, ); @@ -990,12 +990,12 @@ assert_type(g(""), int) # E: `Literal['']` is not assignable to parameter with testcase!( test_generic_callable_or_none, r#" -from typing import assert_type, Callable +from typing import Literal, assert_type, Callable def f[T]() -> Callable[[T], T] | None: ... g = f() if g: - assert_type(g(0), int) - assert_type(g(""), str) + assert_type(g(0), Literal[0]) + assert_type(g(""), Literal['']) "#, ); diff --git a/pyrefly/lib/test/calls.rs b/pyrefly/lib/test/calls.rs index ba1bf83e7d..143ed8be92 100644 --- a/pyrefly/lib/test/calls.rs +++ b/pyrefly/lib/test/calls.rs @@ -13,30 +13,30 @@ use crate::testcase; testcase!( test_generic_call_happy_case, r#" -from typing import Never +from typing import Never, Literal def force_error(x: Never) -> None: ... def f[S, T](x: S, y: T) -> tuple[S, T]: ... -force_error(f(1, "foo")) # E: Argument `tuple[int, str]` is not assignable to parameter `x` +force_error(f(1, "foo")) # E: Argument `tuple[Literal[1], Literal['foo']]` is not assignable to parameter `x` "#, ); testcase!( test_generic_call_fails_to_solve_output_var_simple, r#" -from typing import Never +from typing import Never, Literal def force_error(x: Never) -> None: ... def f[S, T](x: S) -> tuple[S, T]: ... -force_error(f(1)) # E: Argument `tuple[int, @_]` is not assignable to parameter `x` +force_error(f(1)) # E: Argument `tuple[Literal[1], @_]` is not assignable to parameter `x` "#, ); testcase!( test_generic_call_fails_to_solve_output_var_union_case, r#" -from typing import Never +from typing import Never, Literal def force_error(x: Never) -> None: ... def f[S, T](x: S, y: list[T] | None) -> tuple[S, T]: ... -force_error(f(1, None)) # E: Argument `tuple[int, @_]` is not assignable to parameter `x` +force_error(f(1, None)) # E: Argument `tuple[Literal[1], @_]` is not assignable to parameter `x` "#, ); diff --git a/pyrefly/lib/test/decorators.rs b/pyrefly/lib/test/decorators.rs index b8e23b9c89..ca0278c311 100644 --- a/pyrefly/lib/test/decorators.rs +++ b/pyrefly/lib/test/decorators.rs @@ -309,7 +309,7 @@ class C: testcase!( test_decorate_generic_function, r#" -from typing import assert_type +from typing import Literal, assert_type def decorate[T](f: T) -> T: return f @@ -319,7 +319,7 @@ class C: def f[T](self, x: T) -> T: return x -assert_type(C().f(0), int) +assert_type(C().f(0), Literal[0]) "#, ); diff --git a/pyrefly/lib/test/generic_legacy.rs b/pyrefly/lib/test/generic_legacy.rs index 801a5c0874..33d7257c70 100644 --- a/pyrefly/lib/test/generic_legacy.rs +++ b/pyrefly/lib/test/generic_legacy.rs @@ -795,7 +795,7 @@ testcase!( "from typing import TypeVar\nT = TypeVar('T')\nclass C: pass" ), r#" -from typing import Generic, assert_type +from typing import Generic, Literal, assert_type import foo # Here, the `foo.C` possible-legacy-tparam binding is the one that winds up in scope, we @@ -804,7 +804,7 @@ import foo def f(x: foo.T, y: foo.C) -> foo.T: z: foo.T = x # E: `T` is not assignable to `TypeVar[T]` return z # E: Returned type `TypeVar[T]` is not assignable to declared return type `T -assert_type(f(1, foo.C()), int) +assert_type(f(1, foo.C()), Literal[1]) # The same thing happens here, but it's a much bigger problem because now we forget # about the type variable identity for the entire class body, so the signatures come out From 8f1cdf9a7215f1421664965a6c34b35d6e6404e9 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Tue, 4 Nov 2025 23:26:41 +0900 Subject: [PATCH 3/8] fix some --- pyrefly/lib/test/pydantic/root_model.rs | 4 ++-- pyrefly/lib/test/simple.rs | 8 ++++---- pyrefly/lib/test/type_var_tuple.rs | 4 ++-- pyrefly/lib/test/typed_dict.rs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pyrefly/lib/test/pydantic/root_model.rs b/pyrefly/lib/test/pydantic/root_model.rs index c7d12fc386..cb23f5c83d 100644 --- a/pyrefly/lib/test/pydantic/root_model.rs +++ b/pyrefly/lib/test/pydantic/root_model.rs @@ -106,13 +106,13 @@ pydantic_testcase!( "we should not error on the call with a str argument because it could be coercible to int ", test_directly_use_root_model, r#" -from typing import Any, assert_type +from typing import Any, assert_type, Literal from pydantic import RootModel m1 = RootModel() assert_type(m1, RootModel[Any]) m2 = RootModel(5) -assert_type(m2, RootModel[int]) +assert_type(m2, RootModel[Literal[5]]) RootModel(5, extra=6) # E: Unexpected keyword argument `extra` m3 = RootModel[int](5) diff --git a/pyrefly/lib/test/simple.rs b/pyrefly/lib/test/simple.rs index 245794f1fe..cbd9de097c 100644 --- a/pyrefly/lib/test/simple.rs +++ b/pyrefly/lib/test/simple.rs @@ -358,10 +358,10 @@ reveal_type(1) # E: revealed type: Literal[1] testcase!( test_reveal_type_expand_var, r#" -from typing import reveal_type +from typing import reveal_type, Literal def f[T](x: T) -> T: return x -reveal_type(f(0)) # E: revealed type: int +reveal_type(f(0)) # E: revealed type: Literal[0] "#, ); @@ -1284,11 +1284,11 @@ def foo(a: list[str] | Sequence[str]) -> None: testcase!( test_nonint_slice, r#" -from typing import assert_type +from typing import assert_type, Literal class A: def __getitem__[T](self, x: T) -> T: return x -assert_type(A()["":"":""], slice[str, str, str]) +assert_type(A()["":"":""], slice[Literal[''], Literal[''], Literal['']]) "#, ); diff --git a/pyrefly/lib/test/type_var_tuple.rs b/pyrefly/lib/test/type_var_tuple.rs index 9e532031cc..5aff2a5544 100644 --- a/pyrefly/lib/test/type_var_tuple.rs +++ b/pyrefly/lib/test/type_var_tuple.rs @@ -106,12 +106,12 @@ def test(a1: A[int], a2: A[int, str], b: B[int, str, int]): testcase!( test_type_var_tuple_solve, r#" -from typing import assert_type +from typing import assert_type, Literal class A[*Ts]: def x(self) -> tuple[*Ts]: ... def test[*Ts](x: tuple[*Ts]) -> tuple[*Ts]: return x -assert_type(test((1, 2, 3)), tuple[int, int, int]) +assert_type(test((1, 2, 3)), tuple[Literal[1], Literal[2], Literal[3]]) "#, ); diff --git a/pyrefly/lib/test/typed_dict.rs b/pyrefly/lib/test/typed_dict.rs index f4a566c9c5..36ac4ecd8b 100644 --- a/pyrefly/lib/test/typed_dict.rs +++ b/pyrefly/lib/test/typed_dict.rs @@ -811,7 +811,7 @@ class C(TypedDict): def f(c: C, k1: str, k2: int): assert_type(c.get("x"), int) assert_type(c.get(k1), object | None) - assert_type(c.get(k1, 0), int | object) + assert_type(c.get(k1, 0), Literal[0] | object) c.get(k2) # E: No matching overload "#, ); From 2d7569aa5002bd3643456dce074fc92b4136be5c Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Tue, 4 Nov 2025 23:52:09 +0900 Subject: [PATCH 4/8] some more fix --- pyrefly/lib/alt/attr.rs | 6 ++++++ pyrefly/lib/alt/call.rs | 8 ++++++++ pyrefly/lib/solver/solver.rs | 34 ++++++++++++++++++++++++++++++++-- pyrefly/lib/test/paramspec.rs | 4 ++-- pyrefly/lib/test/typed_dict.rs | 14 +++++++------- 5 files changed, 55 insertions(+), 11 deletions(-) diff --git a/pyrefly/lib/alt/attr.rs b/pyrefly/lib/alt/attr.rs index e69c1dbcab..517044ab12 100644 --- a/pyrefly/lib/alt/attr.rs +++ b/pyrefly/lib/alt/attr.rs @@ -1576,6 +1576,12 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { Type::Literal(lit) => acc.push(AttributeBase1::ClassInstance( lit.general_class_type(self.stdlib).clone(), )), + Type::Type(box Type::Literal(lit)) => acc.push(AttributeBase1::ClassObject( + ClassBase::ClassType(lit.general_class_type(self.stdlib).clone()), + )), + Type::Type(box Type::LiteralString) => acc.push(AttributeBase1::ClassObject( + ClassBase::ClassType(self.stdlib.str().clone()), + )), Type::TypeGuard(_) | Type::TypeIs(_) => { acc.push(AttributeBase1::ClassInstance(self.stdlib.bool().clone())) } diff --git a/pyrefly/lib/alt/call.rs b/pyrefly/lib/alt/call.rs index a971feb6ba..28c8def462 100644 --- a/pyrefly/lib/alt/call.rs +++ b/pyrefly/lib/alt/call.rs @@ -597,6 +597,10 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { ); self.solver() .finish_class_targs(typed_dict.targs_mut(), self.uniques); + typed_dict.targs_mut().as_mut().iter_mut().for_each(|targ| { + let promoted = targ.clone().promote_literals(self.stdlib); + *targ = promoted; + }); Type::TypedDict(typed_dict) } @@ -796,6 +800,10 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { style.propagate() } }; + let res = match res { + Type::Union(members) => self.unions(members), + other => other, + }; if let Some(func_metadata) = kw_metadata { let mut kws = TypeMap::new(); for kw in keywords { diff --git a/pyrefly/lib/solver/solver.rs b/pyrefly/lib/solver/solver.rs index 9fe885e21b..90e304fa21 100644 --- a/pyrefly/lib/solver/solver.rs +++ b/pyrefly/lib/solver/solver.rs @@ -1196,7 +1196,22 @@ impl<'a, Ans: LookupAnswer> Subset<'a, Ans> { let t1 = t1.clone(); drop(v1_ref); drop(variables); - self.is_subset_eq(&t1, t2) + match self.is_subset_eq(&t1, t2) { + Ok(()) => Ok(()), + Err(err) => { + let t1_promoted = + t1.clone().promote_literals(self.type_order.stdlib()); + if t1_promoted != t1 { + self.solver + .variables + .lock() + .update(*v1, Variable::Answer(t1_promoted.clone())); + self.is_subset_eq(&t1_promoted, t2) + } else { + Err(err) + } + } + } } Variable::Quantified(q) => { let name = q.name.clone(); @@ -1238,7 +1253,22 @@ impl<'a, Ans: LookupAnswer> Subset<'a, Ans> { let t2 = t2.clone(); drop(v2_ref); drop(variables); - self.is_subset_eq(t1, &t2) + match self.is_subset_eq(t1, &t2) { + Ok(()) => Ok(()), + Err(err) => { + let t2_promoted = + t2.clone().promote_literals(self.type_order.stdlib()); + if t2_promoted != t2 { + self.solver + .variables + .lock() + .update(*v2, Variable::Answer(t2_promoted.clone())); + self.is_subset_eq(t1, &t2_promoted) + } else { + Err(err) + } + } + } } Variable::Quantified(q) => { let t1_p = t1.clone().promote_literals(self.type_order.stdlib()); diff --git a/pyrefly/lib/test/paramspec.rs b/pyrefly/lib/test/paramspec.rs index 78129d7be8..f158983a3f 100644 --- a/pyrefly/lib/test/paramspec.rs +++ b/pyrefly/lib/test/paramspec.rs @@ -207,7 +207,7 @@ foo(keyword_only_x, keyword_only_y) # Rejected # E: Argument `(*, y: int) -> int testcase!( test_paramspec_constructor, r#" -from typing import TypeVar, Generic, Callable, ParamSpec, reveal_type +from typing import TypeVar, Generic, Callable, ParamSpec, reveal_type, Literal P = ParamSpec("P") U = TypeVar("U") @@ -222,7 +222,7 @@ class Y(Generic[U, P]): def a(q: int) -> str: ... -reveal_type(Y(a, 1)) # E: revealed type: Y[int, [q: int]] +reveal_type(Y(a, 1)) # E: revealed type: Y[Literal[1], [q: int]] reveal_type(Y(a, 1).f) # E: revealed type: (q: int) -> str "#, ); diff --git a/pyrefly/lib/test/typed_dict.rs b/pyrefly/lib/test/typed_dict.rs index 36ac4ecd8b..ff1a5349b5 100644 --- a/pyrefly/lib/test/typed_dict.rs +++ b/pyrefly/lib/test/typed_dict.rs @@ -323,7 +323,7 @@ def f(td: TD): testcase!( test_typed_dict_pop_2, r#" -from typing import TypedDict, NotRequired, assert_type, Any +from typing import TypedDict, NotRequired, assert_type, Any, Literal class TDRequired(TypedDict): a: int @@ -354,7 +354,7 @@ v4 = td_o.pop("x", -1) assert_type(v4, int) v5 = td_o.pop("x", "fallback") -assert_type(v5, int | str) +assert_type(v5, int | Literal['fallback']) v6 = td_m.pop("a") # E: assert_type(v6, Any) @@ -366,7 +366,7 @@ v8 = td_m.pop("x", 0) assert_type(v8, int) v9 = td_m.pop("x", "fallback") -assert_type(v9, int | str) +assert_type(v9, int | Literal['fallback']) v10 = td_r.pop("abc", 123) # E: assert_type(v10, object) @@ -1707,13 +1707,13 @@ def f(a: A): testcase!( test_pop_extra_items, r#" -from typing import assert_type, NotRequired, TypedDict +from typing import assert_type, NotRequired, TypedDict, Literal class A(TypedDict, extra_items=int): x: NotRequired[str] def f(a: A, k: str): assert_type(a.pop('x'), str) assert_type(a.pop(k), str | int) - assert_type(a.pop(k, b'default'), str | int | bytes) + assert_type(a.pop(k, b'default'), str | int | Literal[b'default']) "#, ); @@ -1732,12 +1732,12 @@ def f(a: A, k: str): testcase!( test_get_extra_items, r#" -from typing import assert_type, TypedDict +from typing import assert_type, TypedDict, Literal class A(TypedDict, extra_items=int): x: str def f(a: A, k: str): assert_type(a.get(k), str | int | None) - assert_type(a.get(k, b'hello world'), str | int | bytes) + assert_type(a.get(k, b'hello world'), str | int | Literal[b'hello world']) "#, ); From e1a5358485af8febf8fe61ec4b8926ae03aba566 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Wed, 5 Nov 2025 00:11:07 +0900 Subject: [PATCH 5/8] fix some --- pyrefly/lib/test/generic_legacy.rs | 16 ++++++++-------- pyrefly/lib/test/literal.rs | 6 +++--- pyrefly/lib/test/narrow.rs | 4 ++-- pyrefly/lib/test/natural.rs | 4 ++-- pyrefly/lib/test/yields.rs | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/pyrefly/lib/test/generic_legacy.rs b/pyrefly/lib/test/generic_legacy.rs index 33d7257c70..bb4c8cb72d 100644 --- a/pyrefly/lib/test/generic_legacy.rs +++ b/pyrefly/lib/test/generic_legacy.rs @@ -11,7 +11,7 @@ use crate::testcase; testcase!( test_tyvar_function, r#" -from typing import TypeVar, assert_type +from typing import TypeVar, assert_type, Literal T = TypeVar("T") @@ -19,14 +19,14 @@ def foo(x: T) -> T: y: T = x return y -assert_type(foo(1), int) +assert_type(foo(1), Literal[1]) "#, ); testcase!( test_tyvar_alias, r#" -from typing import assert_type +from typing import assert_type, Literal import typing T = typing.TypeVar("T") @@ -34,14 +34,14 @@ T = typing.TypeVar("T") def foo(x: T) -> T: return x -assert_type(foo(1), int) +assert_type(foo(1), Literal[1]) "#, ); testcase!( test_tyvar_quoted, r#" -from typing import assert_type +from typing import assert_type, Literal import typing T = typing.TypeVar("T") @@ -49,7 +49,7 @@ T = typing.TypeVar("T") def foo(x: "T") -> "T": return x -assert_type(foo(1), int) +assert_type(foo(1), Literal[1]) "#, ); @@ -726,11 +726,11 @@ testcase!( test_legacy_typevar_imported_after_use, TestEnv::one("foo", "from typing import TypeVar\nT = TypeVar('T')"), r#" -from typing import assert_type +from typing import assert_type, Literal def f(x: "foo.T") -> "foo.T": return x import foo -assert_type(f(0), int) +assert_type(f(0), Literal[0]) "#, ); diff --git a/pyrefly/lib/test/literal.rs b/pyrefly/lib/test/literal.rs index 94b1678336..a44471d1f0 100644 --- a/pyrefly/lib/test/literal.rs +++ b/pyrefly/lib/test/literal.rs @@ -68,7 +68,7 @@ class Foo[T]: def __init__(self, x: T) -> None: ... x: Literal[42] = 42 -assert_type(Foo(x), Foo[int]) +assert_type(Foo(x), Foo[Literal[42]]) "#, ); @@ -224,10 +224,10 @@ testcase!( testcase!( test_promote_literal, r#" -from typing import assert_type, Literal +from typing import assert_type, Literal, LiteralString x = list("abcdefg") -assert_type(x, list[str]) +assert_type(x, list[LiteralString]) "#, ); diff --git a/pyrefly/lib/test/narrow.rs b/pyrefly/lib/test/narrow.rs index 303ffd96c2..63ae3c8573 100644 --- a/pyrefly/lib/test/narrow.rs +++ b/pyrefly/lib/test/narrow.rs @@ -986,14 +986,14 @@ def f(c: C, x: int | str): testcase!( test_typeguard_generic_function, r#" -from typing import TypeGuard, assert_type +from typing import TypeGuard, assert_type, Literal def f[T](x: object, y: T, z: T) -> TypeGuard[int]: ... def f2[T](x: object, y: T) -> TypeGuard[T]: ... def g(x: int | str): if f(x, 0, 0): assert_type(x, int) if f2(x, ""): - assert_type(x, str) + assert_type(x, Literal['']) "#, ); diff --git a/pyrefly/lib/test/natural.rs b/pyrefly/lib/test/natural.rs index 5576521c35..4c1e6f34c8 100644 --- a/pyrefly/lib/test/natural.rs +++ b/pyrefly/lib/test/natural.rs @@ -113,13 +113,13 @@ assert_type(foo(), Literal[42]) testcase!( test_generic_inference, r#" -from typing import assert_type +from typing import assert_type, Literal class C[T]: def __init__(self, x: T) -> None: pass -assert_type(C(42), C[int]) +assert_type(C(42), C[Literal[42]]) "#, ); diff --git a/pyrefly/lib/test/yields.rs b/pyrefly/lib/test/yields.rs index d77df761fb..1e79062140 100644 --- a/pyrefly/lib/test/yields.rs +++ b/pyrefly/lib/test/yields.rs @@ -18,7 +18,7 @@ def yielding(): f = yielding() next_f = next(f) -assert_type(next_f, int) +assert_type(next_f, Literal[1]) assert_type(f, Generator[Literal[1], Any, None]) "#, From 4da45c981a23f9dbccb2650edad9b8eac2f20a75 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Wed, 5 Nov 2025 00:39:01 +0900 Subject: [PATCH 6/8] fix --- pyrefly/lib/test/callable.rs | 6 +++--- pyrefly/lib/test/constructors.rs | 28 ++++++++++++++-------------- pyrefly/lib/test/dataclasses.rs | 10 +++++----- pyrefly/lib/test/enums.rs | 4 ++-- pyrefly/lib/test/generic_basic.rs | 4 ++-- pyrefly/lib/test/yields.rs | 4 ++-- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/pyrefly/lib/test/callable.rs b/pyrefly/lib/test/callable.rs index 328a15f2f7..6bee099782 100644 --- a/pyrefly/lib/test/callable.rs +++ b/pyrefly/lib/test/callable.rs @@ -979,11 +979,11 @@ assert_type(g(""), Literal['']) testcase!( test_return_substituted_callable, r#" -from typing import assert_type, Callable +from typing import Literal, assert_type, Callable def f[T](x: T) -> Callable[[T], T]: ... g = f(0) -assert_type(g(0), int) -assert_type(g(""), int) # E: `Literal['']` is not assignable to parameter with type `int` +assert_type(g(0), Literal[0]) +assert_type(g(""), Literal[0]) # E: `Literal['']` is not assignable to parameter with type `Literal[0]` "#, ); diff --git a/pyrefly/lib/test/constructors.rs b/pyrefly/lib/test/constructors.rs index cf26828cbe..c7d8898724 100644 --- a/pyrefly/lib/test/constructors.rs +++ b/pyrefly/lib/test/constructors.rs @@ -196,13 +196,13 @@ testcase!( bug = "Missing check that self/cls param is a supertype of the defining class", test_metaclass_call_cls_param_does_not_instantiate, r#" -from typing import assert_type +from typing import Literal, assert_type class Meta(type): def __call__(cls: 'type[C[str]]', *args, **kwargs): ... # TODO: error because annot is not supertype of Meta class C[T](metaclass=Meta): def __init__(self, x: T): pass -assert_type(C(0), C[int]) # Correct, because metaclass call does not instantiate T=str +assert_type(C(0), C[Literal[0]]) # Correct, preserves the literal passed to C "#, ); @@ -211,14 +211,14 @@ assert_type(C(0), C[int]) # Correct, because metaclass call does not instantiate testcase!( test_metaclass_call_does_not_instantiate, r#" -from typing import assert_type +from typing import Literal, assert_type class Meta(type): def __call__(cls, *args, **kwargs) -> 'C[str]': ... class C[T](metaclass=Meta): def __init__(self, x: T): pass -assert_type(C(0), C[int]) +assert_type(C(0), C[Literal[0]]) "#, ); @@ -312,12 +312,12 @@ assert_type(x, C) testcase!( test_new_returns_something_else_generic, r#" -from typing import assert_type +from typing import Literal, assert_type class C[T]: def __new__(cls, x: T) -> list[T]: return [] x = C(0) -assert_type(x, list[int]) +assert_type(x, list[Literal[0]]) "#, ); @@ -477,10 +477,10 @@ assert_type(C2(), C2[int]) testcase!( test_specialize_in_new, r#" -from typing import assert_type +from typing import Literal, assert_type class C[T]: def __new__[T2](cls, x: T2) -> C[T2]: ... -assert_type(C(0), C[int]) +assert_type(C(0), C[Literal[0]]) "#, ); @@ -501,26 +501,26 @@ assert_type(A(int), A[int]) testcase!( test_overload_init, r#" -from typing import overload, assert_type +from typing import Literal, overload, assert_type class C[T]: @overload def __init__(self, x: T, y: int): ... # E: Overloaded function must have an implementation @overload def __init__(self, x: int, y: T): ... -assert_type(C(0, "foo"), C[str]) +assert_type(C(0, "foo"), C[Literal['foo']]) "#, ); testcase!( test_new_and_init_generic, r#" -from typing import Self,assert_type +from typing import Literal, Self, assert_type class Class2[T]: def __new__(cls, *args, **kwargs) -> Self: ... def __init__(self, x: T) -> None: ... -assert_type(Class2(1), Class2[int]) +assert_type(Class2(1), Class2[Literal[1]]) "#, ); @@ -534,7 +534,7 @@ assert_type(Class2(1), Class2[int]) testcase!( test_new_and_init_partial_instantiation, r#" -from typing import Any, Self, assert_type +from typing import Any, Literal, Self, assert_type class C[T, U]: def __new__(cls, x: T, y: Any) -> Self: @@ -543,7 +543,7 @@ class C[T, U]: def __init__(self, x: Any, y: U): pass -assert_type(C(0, ""), C[int, str]) +assert_type(C(0, ""), C[Literal[0], Literal['']]) "#, ); diff --git a/pyrefly/lib/test/dataclasses.rs b/pyrefly/lib/test/dataclasses.rs index e1fee9c6af..2b0233f380 100644 --- a/pyrefly/lib/test/dataclasses.rs +++ b/pyrefly/lib/test/dataclasses.rs @@ -13,7 +13,7 @@ use crate::testcase; testcase!( test_def, r#" -from typing import assert_type +from typing import Literal, assert_type import dataclasses @dataclasses.dataclass class Data: @@ -26,7 +26,7 @@ assert_type(Data, type[Data]) testcase!( test_fields, r#" -from typing import assert_type +from typing import Literal, assert_type import dataclasses @dataclasses.dataclass class Data: @@ -41,14 +41,14 @@ def f(d: Data): testcase!( test_generic, r#" -from typing import assert_type +from typing import Literal, assert_type import dataclasses @dataclasses.dataclass class Data[T]: x: T def f(d: Data[int]): assert_type(d.x, int) -assert_type(Data(x=0), Data[int]) +assert_type(Data(x=0), Data[Literal[0]]) Data[int](x=0) # OK Data[int](x="") # E: Argument `Literal['']` is not assignable to parameter `x` with type `int` in function `Data.__init__` "#, @@ -438,7 +438,7 @@ C2(x=1) # OK @dataclass class C3: - x: int = field(default="oops") # E: `str` is not assignable to `int` + x: int = field(default="oops") # E: `Literal['oops']` is not assignable to `int` y: str = field(default_factory=factory) # E: `int` is not assignable to `str` "#, ); diff --git a/pyrefly/lib/test/enums.rs b/pyrefly/lib/test/enums.rs index 6eace8da75..4559ebd4c0 100644 --- a/pyrefly/lib/test/enums.rs +++ b/pyrefly/lib/test/enums.rs @@ -152,7 +152,7 @@ class MyEnum(Enum): W = auto() X = 1 Y = "FOO" # E: Enum member `Y` has type `str`, must match the `_value_` attribute annotation of `int` - Z = member("FOO") # E: Enum member `Z` has type `str`, must match the `_value_` attribute annotation of `int` + Z = member("FOO") # E: Enum member `Z` has type `Literal['FOO']`, must match the `_value_` attribute annotation of `int` def get_value(self) -> int: if self.value > 0: @@ -222,7 +222,7 @@ class MyEnum(Enum): def D(self) -> None: pass reveal_type(MyEnum.A) # E: revealed type: Literal[MyEnum.A] -reveal_type(MyEnum.B) # E: revealed type: nonmember[int] +reveal_type(MyEnum.B) # E: revealed type: nonmember[Literal[2]] reveal_type(MyEnum.C) # E: revealed type: Literal[MyEnum.C] reveal_type(MyEnum.D) # E: revealed type: (self: MyEnum) -> None "#, diff --git a/pyrefly/lib/test/generic_basic.rs b/pyrefly/lib/test/generic_basic.rs index e594dcc616..492ea12c46 100644 --- a/pyrefly/lib/test/generic_basic.rs +++ b/pyrefly/lib/test/generic_basic.rs @@ -30,14 +30,14 @@ def test2[T: A](cls: type[T]) -> T: testcase!( test_tyvar_mix, r#" -from typing import TypeVar, assert_type +from typing import Literal, TypeVar, assert_type U = TypeVar("U") def foo[T]( x: U # E: Type parameter U is not included in the type parameter list ) -> U: return x -assert_type(foo(1), int) +assert_type(foo(1), Literal[1]) "#, ); diff --git a/pyrefly/lib/test/yields.rs b/pyrefly/lib/test/yields.rs index 1e79062140..4eed4a8449 100644 --- a/pyrefly/lib/test/yields.rs +++ b/pyrefly/lib/test/yields.rs @@ -165,7 +165,7 @@ assert_type(another_generator(), Generator[Literal[2], Any, None]) testcase!( test_parametric_generator_type, r#" -from typing import Generator, TypeVar, assert_type +from typing import Generator, Literal, TypeVar, assert_type T = TypeVar('T') @@ -173,7 +173,7 @@ def f(value: T) -> Generator[T, None, None]: while True: yield value -assert_type(f(3), Generator[int, None, None]) +assert_type(f(3), Generator[Literal[3], None, None]) "#, ); From de2d396a24b7e7bc209dd037547f2dd99af890cc Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Wed, 5 Nov 2025 01:00:23 +0900 Subject: [PATCH 7/8] class parameter back to A[int] --- pyrefly/lib/alt/call.rs | 27 ++++++++++++++++++++++----- pyrefly/lib/test/decorators.rs | 4 ++-- pyrefly/lib/test/generic_basic.rs | 11 +++++++++++ pyrefly/lib/test/generic_legacy.rs | 4 ++-- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/pyrefly/lib/alt/call.rs b/pyrefly/lib/alt/call.rs index 28c8def462..f6eb193c6f 100644 --- a/pyrefly/lib/alt/call.rs +++ b/pyrefly/lib/alt/call.rs @@ -48,6 +48,7 @@ use crate::types::class::ClassType; use crate::types::keywords::KwCall; use crate::types::keywords::TypeMap; use crate::types::literal::Lit; +use crate::types::type_var::PreInferenceVariance; use crate::types::type_var::Restriction; use crate::types::typed_dict::TypedDict; use crate::types::types::AnyStyle; @@ -444,6 +445,7 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { ); self.solver() .finish_class_targs(&mut ctor_targs, self.uniques); + self.promote_invariant_targs(&mut ctor_targs); ret.subst_mut(&ctor_targs.substitution_map()); Some(ret) } @@ -512,9 +514,13 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { // According to the spec, the actual type (as opposed to the class under construction) // should take priority. However, if the actual type comes from a type error or an implicit // Any, using the class under construction is still more useful. - self.solver() - .finish_class_targs(cls.targs_mut(), self.uniques); - return ret.subst(&cls.targs().substitution_map()); + { + let targs = cls.targs_mut(); + self.solver().finish_class_targs(targs, self.uniques); + self.promote_invariant_targs(targs); + } + let substitution = cls.targs().substitution_map(); + return ret.subst(&substitution); } (true, has_errors) } else { @@ -549,8 +555,11 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { // Not quite an overload, but close enough self.record_overload_trace_from_type(range, init_method); } - self.solver() - .finish_class_targs(cls.targs_mut(), self.uniques); + { + let targs = cls.targs_mut(); + self.solver().finish_class_targs(targs, self.uniques); + self.promote_invariant_targs(targs); + } if let Some(mut ret) = dunder_new_ret { ret.subst_mut(&cls.targs().substitution_map()); ret @@ -559,6 +568,14 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { } } + fn promote_invariant_targs(&self, targs: &mut TArgs) { + targs.iter_paired_mut().for_each(|(param, targ)| { + if !matches!(param.variance, PreInferenceVariance::PCovariant) { + *targ = targ.clone().promote_literals(self.stdlib); + } + }); + } + fn construct_typed_dict( &self, mut typed_dict: TypedDict, diff --git a/pyrefly/lib/test/decorators.rs b/pyrefly/lib/test/decorators.rs index ca0278c311..b6242d1b3d 100644 --- a/pyrefly/lib/test/decorators.rs +++ b/pyrefly/lib/test/decorators.rs @@ -270,7 +270,7 @@ assert_type(C().f("any", b"thing"), Any) testcase!( test_decorate_to_generic_callable, r#" -from typing import Any, Callable, TypeVar, assert_type +from typing import Any, Callable, TypeVar, assert_type, Literal T = TypeVar('T') def decorate(f) -> Callable[[Any, T], T]: @@ -280,7 +280,7 @@ class C: @decorate def f(self): ... -assert_type(C().f(0), int) +assert_type(C().f(0), Literal[0]) "#, ); diff --git a/pyrefly/lib/test/generic_basic.rs b/pyrefly/lib/test/generic_basic.rs index 492ea12c46..51cbb7ea6b 100644 --- a/pyrefly/lib/test/generic_basic.rs +++ b/pyrefly/lib/test/generic_basic.rs @@ -142,6 +142,17 @@ assert_type(f(1), Literal[1]) "#, ); +testcase!( + test_invariant_generic_literal_widens, + r#" +from typing import assert_type +class A[T]: + def __init__(self, x: T) -> None: ... +a = A(1) +assert_type(a, A[int]) +"#, +); + testcase!( test_untype_with_missing_targs_annotation, TestEnv::new().enable_implicit_any_error(), diff --git a/pyrefly/lib/test/generic_legacy.rs b/pyrefly/lib/test/generic_legacy.rs index bb4c8cb72d..a3a1bcc21b 100644 --- a/pyrefly/lib/test/generic_legacy.rs +++ b/pyrefly/lib/test/generic_legacy.rs @@ -680,11 +680,11 @@ testcase!( env_exported_type_var(), r#" import lib -from typing import assert_type +from typing import assert_type, Literal def f(x: lib.T) -> lib.T: return x -assert_type(f(0), int) +assert_type(f(0), Literal[0]) "#, ); From 2ec6bebf9cab9197cf33f5776d7b1821db4fe52b Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Wed, 5 Nov 2025 01:22:19 +0900 Subject: [PATCH 8/8] fix --- pyrefly/lib/test/constructors.rs | 24 ++++++++++++------------ pyrefly/lib/test/contextual.rs | 6 +++--- pyrefly/lib/test/dataclasses.rs | 4 ++-- pyrefly/lib/test/enums.rs | 4 ++-- pyrefly/lib/test/literal.rs | 10 +++++----- pyrefly/lib/test/natural.rs | 4 ++-- pyrefly/lib/test/paramspec.rs | 4 ++-- pyrefly/lib/test/pydantic/root_model.rs | 4 ++-- 8 files changed, 30 insertions(+), 30 deletions(-) diff --git a/pyrefly/lib/test/constructors.rs b/pyrefly/lib/test/constructors.rs index c7d8898724..3f938d9b58 100644 --- a/pyrefly/lib/test/constructors.rs +++ b/pyrefly/lib/test/constructors.rs @@ -196,13 +196,13 @@ testcase!( bug = "Missing check that self/cls param is a supertype of the defining class", test_metaclass_call_cls_param_does_not_instantiate, r#" -from typing import Literal, assert_type +from typing import assert_type class Meta(type): def __call__(cls: 'type[C[str]]', *args, **kwargs): ... # TODO: error because annot is not supertype of Meta class C[T](metaclass=Meta): def __init__(self, x: T): pass -assert_type(C(0), C[Literal[0]]) # Correct, preserves the literal passed to C +assert_type(C(0), C[int]) # Correct, preserves the argument type passed to C "#, ); @@ -211,14 +211,14 @@ assert_type(C(0), C[Literal[0]]) # Correct, preserves the literal passed to C testcase!( test_metaclass_call_does_not_instantiate, r#" -from typing import Literal, assert_type +from typing import assert_type class Meta(type): def __call__(cls, *args, **kwargs) -> 'C[str]': ... class C[T](metaclass=Meta): def __init__(self, x: T): pass -assert_type(C(0), C[Literal[0]]) +assert_type(C(0), C[int]) "#, ); @@ -312,12 +312,12 @@ assert_type(x, C) testcase!( test_new_returns_something_else_generic, r#" -from typing import Literal, assert_type +from typing import assert_type class C[T]: def __new__(cls, x: T) -> list[T]: return [] x = C(0) -assert_type(x, list[Literal[0]]) +assert_type(x, list[int]) "#, ); @@ -501,26 +501,26 @@ assert_type(A(int), A[int]) testcase!( test_overload_init, r#" -from typing import Literal, overload, assert_type +from typing import overload, assert_type class C[T]: @overload def __init__(self, x: T, y: int): ... # E: Overloaded function must have an implementation @overload def __init__(self, x: int, y: T): ... -assert_type(C(0, "foo"), C[Literal['foo']]) +assert_type(C(0, "foo"), C[str]) "#, ); testcase!( test_new_and_init_generic, r#" -from typing import Literal, Self, assert_type +from typing import Self, assert_type class Class2[T]: def __new__(cls, *args, **kwargs) -> Self: ... def __init__(self, x: T) -> None: ... -assert_type(Class2(1), Class2[Literal[1]]) +assert_type(Class2(1), Class2[int]) "#, ); @@ -534,7 +534,7 @@ assert_type(Class2(1), Class2[Literal[1]]) testcase!( test_new_and_init_partial_instantiation, r#" -from typing import Any, Literal, Self, assert_type +from typing import Any, Self, assert_type class C[T, U]: def __new__(cls, x: T, y: Any) -> Self: @@ -543,7 +543,7 @@ class C[T, U]: def __init__(self, x: Any, y: U): pass -assert_type(C(0, ""), C[Literal[0], Literal['']]) +assert_type(C(0, ""), C[int, str]) "#, ); diff --git a/pyrefly/lib/test/contextual.rs b/pyrefly/lib/test/contextual.rs index 63f8683d2c..e2617df832 100644 --- a/pyrefly/lib/test/contextual.rs +++ b/pyrefly/lib/test/contextual.rs @@ -513,15 +513,15 @@ xs[0] = [B()] testcase!( test_generic_get_literal, r#" -from typing import assert_type, TypeVar, Literal +from typing import TypeVar, assert_type class Foo[T]: def __init__(self, x: T) -> None: ... def get(self) -> T: ... # Should propagate the context to the argument 42 -x: Foo[Literal[42]] = Foo(42) -assert_type(x.get(), Literal[42]) +x: Foo[int] = Foo(42) +assert_type(x.get(), int) "#, ); diff --git a/pyrefly/lib/test/dataclasses.rs b/pyrefly/lib/test/dataclasses.rs index 2b0233f380..13fac4ab3f 100644 --- a/pyrefly/lib/test/dataclasses.rs +++ b/pyrefly/lib/test/dataclasses.rs @@ -41,14 +41,14 @@ def f(d: Data): testcase!( test_generic, r#" -from typing import Literal, assert_type +from typing import assert_type import dataclasses @dataclasses.dataclass class Data[T]: x: T def f(d: Data[int]): assert_type(d.x, int) -assert_type(Data(x=0), Data[Literal[0]]) +assert_type(Data(x=0), Data[int]) Data[int](x=0) # OK Data[int](x="") # E: Argument `Literal['']` is not assignable to parameter `x` with type `int` in function `Data.__init__` "#, diff --git a/pyrefly/lib/test/enums.rs b/pyrefly/lib/test/enums.rs index 4559ebd4c0..6eace8da75 100644 --- a/pyrefly/lib/test/enums.rs +++ b/pyrefly/lib/test/enums.rs @@ -152,7 +152,7 @@ class MyEnum(Enum): W = auto() X = 1 Y = "FOO" # E: Enum member `Y` has type `str`, must match the `_value_` attribute annotation of `int` - Z = member("FOO") # E: Enum member `Z` has type `Literal['FOO']`, must match the `_value_` attribute annotation of `int` + Z = member("FOO") # E: Enum member `Z` has type `str`, must match the `_value_` attribute annotation of `int` def get_value(self) -> int: if self.value > 0: @@ -222,7 +222,7 @@ class MyEnum(Enum): def D(self) -> None: pass reveal_type(MyEnum.A) # E: revealed type: Literal[MyEnum.A] -reveal_type(MyEnum.B) # E: revealed type: nonmember[Literal[2]] +reveal_type(MyEnum.B) # E: revealed type: nonmember[int] reveal_type(MyEnum.C) # E: revealed type: Literal[MyEnum.C] reveal_type(MyEnum.D) # E: revealed type: (self: MyEnum) -> None "#, diff --git a/pyrefly/lib/test/literal.rs b/pyrefly/lib/test/literal.rs index a44471d1f0..56acc7e64f 100644 --- a/pyrefly/lib/test/literal.rs +++ b/pyrefly/lib/test/literal.rs @@ -62,13 +62,13 @@ x: Literal[0xFFFFFFFFFFFFFFFFFF] testcase!( test_generic_create_literal, r#" -from typing import assert_type, Literal +from typing import assert_type class Foo[T]: def __init__(self, x: T) -> None: ... -x: Literal[42] = 42 -assert_type(Foo(x), Foo[Literal[42]]) +x = 42 +assert_type(Foo(x), Foo[int]) "#, ); @@ -224,10 +224,10 @@ testcase!( testcase!( test_promote_literal, r#" -from typing import assert_type, Literal, LiteralString +from typing import assert_type x = list("abcdefg") -assert_type(x, list[LiteralString]) +assert_type(x, list[str]) "#, ); diff --git a/pyrefly/lib/test/natural.rs b/pyrefly/lib/test/natural.rs index 4c1e6f34c8..5576521c35 100644 --- a/pyrefly/lib/test/natural.rs +++ b/pyrefly/lib/test/natural.rs @@ -113,13 +113,13 @@ assert_type(foo(), Literal[42]) testcase!( test_generic_inference, r#" -from typing import assert_type, Literal +from typing import assert_type class C[T]: def __init__(self, x: T) -> None: pass -assert_type(C(42), C[Literal[42]]) +assert_type(C(42), C[int]) "#, ); diff --git a/pyrefly/lib/test/paramspec.rs b/pyrefly/lib/test/paramspec.rs index f158983a3f..78129d7be8 100644 --- a/pyrefly/lib/test/paramspec.rs +++ b/pyrefly/lib/test/paramspec.rs @@ -207,7 +207,7 @@ foo(keyword_only_x, keyword_only_y) # Rejected # E: Argument `(*, y: int) -> int testcase!( test_paramspec_constructor, r#" -from typing import TypeVar, Generic, Callable, ParamSpec, reveal_type, Literal +from typing import TypeVar, Generic, Callable, ParamSpec, reveal_type P = ParamSpec("P") U = TypeVar("U") @@ -222,7 +222,7 @@ class Y(Generic[U, P]): def a(q: int) -> str: ... -reveal_type(Y(a, 1)) # E: revealed type: Y[Literal[1], [q: int]] +reveal_type(Y(a, 1)) # E: revealed type: Y[int, [q: int]] reveal_type(Y(a, 1).f) # E: revealed type: (q: int) -> str "#, ); diff --git a/pyrefly/lib/test/pydantic/root_model.rs b/pyrefly/lib/test/pydantic/root_model.rs index cb23f5c83d..c7d12fc386 100644 --- a/pyrefly/lib/test/pydantic/root_model.rs +++ b/pyrefly/lib/test/pydantic/root_model.rs @@ -106,13 +106,13 @@ pydantic_testcase!( "we should not error on the call with a str argument because it could be coercible to int ", test_directly_use_root_model, r#" -from typing import Any, assert_type, Literal +from typing import Any, assert_type from pydantic import RootModel m1 = RootModel() assert_type(m1, RootModel[Any]) m2 = RootModel(5) -assert_type(m2, RootModel[Literal[5]]) +assert_type(m2, RootModel[int]) RootModel(5, extra=6) # E: Unexpected keyword argument `extra` m3 = RootModel[int](5)