From 2e6729d900f06a8fdbb7210b6e800568103cc8a8 Mon Sep 17 00:00:00 2001 From: InSync Date: Fri, 17 Jan 2025 02:21:56 +0700 Subject: [PATCH] [red-knot] Migrate `bool`/`str`/`repr` unit tests to Markdown tests (#15534) ## Summary Part of #15397. ## Test Plan Markdown tests. --- .../mdtest/annotations/literal_string.md | 2 +- .../resources/mdtest/literal/f_string.md | 21 +++--- .../mdtest/type_properties/str_repr.md | 27 +++++++ .../mdtest/type_properties/truthiness.md | 47 ++++++++++++ crates/red_knot_python_semantic/src/types.rs | 72 +++++-------------- .../knot_extensions/knot_extensions.pyi | 2 +- 6 files changed, 105 insertions(+), 66 deletions(-) create mode 100644 crates/red_knot_python_semantic/resources/mdtest/type_properties/str_repr.md create mode 100644 crates/red_knot_python_semantic/resources/mdtest/type_properties/truthiness.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 index b6f1e0ecdc87e..db94217aa2c21 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 @@ -100,7 +100,7 @@ def _(flag: bool): foo_3: LiteralString = "foo" * 1_000_000_000 bar_3: str = foo_2 # fine - baz_1: str = str() + baz_1: str = repr(object()) qux_1: LiteralString = baz_1 # error: [invalid-assignment] baz_2: LiteralString = "baz" * 1_000_000_000 diff --git a/crates/red_knot_python_semantic/resources/mdtest/literal/f_string.md b/crates/red_knot_python_semantic/resources/mdtest/literal/f_string.md index 5be1b2a271d24..63d43501f272a 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/literal/f_string.md +++ b/crates/red_knot_python_semantic/resources/mdtest/literal/f_string.md @@ -3,17 +3,16 @@ ## Expression ```py -x = 0 -y = str() -z = False - -reveal_type(f"hello") # revealed: Literal["hello"] -reveal_type(f"h {x}") # revealed: Literal["h 0"] -reveal_type("one " f"single " f"literal") # revealed: Literal["one single literal"] -reveal_type("first " f"second({x})" f" third") # revealed: Literal["first second(0) third"] -reveal_type(f"-{y}-") # revealed: str -reveal_type(f"-{y}-" f"--" "--") # revealed: str -reveal_type(f"{z} == {False} is {True}") # revealed: Literal["False == False is True"] +from typing_extensions import Literal + +def _(x: Literal[0], y: str, z: Literal[False]): + reveal_type(f"hello") # revealed: Literal["hello"] + reveal_type(f"h {x}") # revealed: Literal["h 0"] + reveal_type("one " f"single " f"literal") # revealed: Literal["one single literal"] + reveal_type("first " f"second({x})" f" third") # revealed: Literal["first second(0) third"] + reveal_type(f"-{y}-") # revealed: str + reveal_type(f"-{y}-" f"--" "--") # revealed: str + reveal_type(f"{z} == {False} is {True}") # revealed: Literal["False == False is True"] ``` ## Conversion Flags diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_properties/str_repr.md b/crates/red_knot_python_semantic/resources/mdtest/type_properties/str_repr.md new file mode 100644 index 0000000000000..1539daabe1dc4 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/type_properties/str_repr.md @@ -0,0 +1,27 @@ +# `__str__` and `__repr__` + +```py +from typing_extensions import Literal, LiteralString + +def _( + a: Literal[1], + b: Literal[True], + c: Literal[False], + d: Literal["ab'cd"], + e: LiteralString, + f: int, +): + reveal_type(str(a)) # revealed: Literal["1"] + reveal_type(str(b)) # revealed: Literal["True"] + reveal_type(str(c)) # revealed: Literal["False"] + reveal_type(str(d)) # revealed: Literal["ab'cd"] + reveal_type(str(e)) # revealed: LiteralString + reveal_type(str(f)) # revealed: str + + reveal_type(repr(a)) # revealed: Literal["1"] + reveal_type(repr(b)) # revealed: Literal["True"] + reveal_type(repr(c)) # revealed: Literal["False"] + reveal_type(repr(d)) # revealed: Literal["'ab\\'cd'"] + reveal_type(repr(e)) # revealed: LiteralString + reveal_type(repr(f)) # revealed: str +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_properties/truthiness.md b/crates/red_knot_python_semantic/resources/mdtest/type_properties/truthiness.md new file mode 100644 index 0000000000000..d2a7f63fd106d --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/type_properties/truthiness.md @@ -0,0 +1,47 @@ +# Truthiness + +```py +from typing_extensions import Literal, LiteralString +from knot_extensions import AlwaysFalsy, AlwaysTruthy + +def _( + a: Literal[1], + b: Literal[-1], + c: Literal["foo"], + d: tuple[Literal[0]], + e: Literal[1, 2], + f: AlwaysTruthy, +): + reveal_type(bool(a)) # revealed: Literal[True] + reveal_type(bool(b)) # revealed: Literal[True] + reveal_type(bool(c)) # revealed: Literal[True] + reveal_type(bool(d)) # revealed: Literal[True] + reveal_type(bool(e)) # revealed: Literal[True] + reveal_type(bool(f)) # revealed: Literal[True] + +def _( + a: tuple[()], + b: Literal[0], + c: Literal[""], + d: Literal[b""], + e: Literal[0, 0], + f: AlwaysFalsy, +): + reveal_type(bool(a)) # revealed: Literal[False] + reveal_type(bool(b)) # revealed: Literal[False] + reveal_type(bool(c)) # revealed: Literal[False] + reveal_type(bool(d)) # revealed: Literal[False] + reveal_type(bool(e)) # revealed: Literal[False] + reveal_type(bool(f)) # revealed: Literal[False] + +def _( + a: str, + b: Literal[1, 0], + c: str | Literal[0], + d: str | Literal[1], +): + reveal_type(bool(a)) # revealed: bool + reveal_type(bool(b)) # revealed: bool + reveal_type(bool(c)) # revealed: bool + reveal_type(bool(d)) # revealed: bool +``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 4e0c7a2e36bf8..a0bb4c725b738 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2013,6 +2013,14 @@ impl<'db> Type<'db> { CallOutcome::callable(binding) } + Some(KnownFunction::Repr) => { + if let Some(first_arg) = binding.one_parameter_ty() { + binding.set_return_ty(first_arg.repr(db)); + }; + + CallOutcome::callable(binding) + } + Some(KnownFunction::AssertType) => { let Some((_, asserted_ty)) = binding.two_parameter_tys() else { return CallOutcome::callable(binding); @@ -2050,6 +2058,12 @@ impl<'db> Type<'db> { .first_argument() .map(|arg| arg.bool(db).into_type(db)) .unwrap_or(Type::BooleanLiteral(false)), + + Some(KnownClass::Str) => arguments + .first_argument() + .map(|arg| arg.str(db)) + .unwrap_or(Type::string_literal(db, "")), + _ => Type::Instance(InstanceType { class }), })) } @@ -3389,6 +3403,8 @@ pub enum KnownFunction { RevealType, /// `builtins.len` Len, + /// `builtins.repr` + Repr, /// `typing(_extensions).final` Final, @@ -3436,6 +3452,7 @@ impl KnownFunction { "issubclass" => Self::ConstraintFunction(KnownConstraintFunction::IsSubclass), "reveal_type" => Self::RevealType, "len" => Self::Len, + "repr" => Self::Repr, "final" => Self::Final, "no_type_check" => Self::NoTypeCheck, "assert_type" => Self::AssertType, @@ -3464,7 +3481,7 @@ impl KnownFunction { module.is_builtins() } }, - Self::Len => module.is_builtins(), + Self::Len | Self::Repr => module.is_builtins(), Self::AssertType | Self::Cast | Self::RevealType | Self::Final | Self::NoTypeCheck => { matches!(module, KnownModule::Typing | KnownModule::TypingExtensions) } @@ -3496,6 +3513,7 @@ impl KnownFunction { Self::ConstraintFunction(_) | Self::Len + | Self::Repr | Self::Final | Self::NoTypeCheck | Self::RevealType @@ -4625,58 +4643,6 @@ pub(crate) mod tests { assert!(!b.is_gradual_equivalent_to(&db, a)); } - #[test_case(Ty::IntLiteral(1); "is_int_literal_truthy")] - #[test_case(Ty::IntLiteral(-1))] - #[test_case(Ty::StringLiteral("foo"))] - #[test_case(Ty::Tuple(vec![Ty::IntLiteral(0)]))] - #[test_case(Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]))] - fn is_truthy(ty: Ty) { - let db = setup_db(); - assert_eq!(ty.into_type(&db).bool(&db), Truthiness::AlwaysTrue); - } - - #[test_case(Ty::Tuple(vec![]))] - #[test_case(Ty::IntLiteral(0))] - #[test_case(Ty::StringLiteral(""))] - #[test_case(Ty::Union(vec![Ty::IntLiteral(0), Ty::IntLiteral(0)]))] - fn is_falsy(ty: Ty) { - let db = setup_db(); - assert_eq!(ty.into_type(&db).bool(&db), Truthiness::AlwaysFalse); - } - - #[test_case(Ty::BuiltinInstance("str"))] - #[test_case(Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(0)]))] - #[test_case(Ty::Union(vec![Ty::BuiltinInstance("str"), Ty::IntLiteral(0)]))] - #[test_case(Ty::Union(vec![Ty::BuiltinInstance("str"), Ty::IntLiteral(1)]))] - fn boolean_value_is_unknown(ty: Ty) { - let db = setup_db(); - assert_eq!(ty.into_type(&db).bool(&db), Truthiness::Ambiguous); - } - - #[test_case(Ty::IntLiteral(1), Ty::StringLiteral("1"))] - #[test_case(Ty::BooleanLiteral(true), Ty::StringLiteral("True"))] - #[test_case(Ty::BooleanLiteral(false), Ty::StringLiteral("False"))] - #[test_case(Ty::StringLiteral("ab'cd"), Ty::StringLiteral("ab'cd"))] // no quotes - #[test_case(Ty::LiteralString, Ty::LiteralString)] - #[test_case(Ty::BuiltinInstance("int"), Ty::BuiltinInstance("str"))] - fn has_correct_str(ty: Ty, expected: Ty) { - let db = setup_db(); - - assert_eq!(ty.into_type(&db).str(&db), expected.into_type(&db)); - } - - #[test_case(Ty::IntLiteral(1), Ty::StringLiteral("1"))] - #[test_case(Ty::BooleanLiteral(true), Ty::StringLiteral("True"))] - #[test_case(Ty::BooleanLiteral(false), Ty::StringLiteral("False"))] - #[test_case(Ty::StringLiteral("ab'cd"), Ty::StringLiteral("'ab\\'cd'"))] // single quotes - #[test_case(Ty::LiteralString, Ty::LiteralString)] - #[test_case(Ty::BuiltinInstance("int"), Ty::BuiltinInstance("str"))] - fn has_correct_repr(ty: Ty, expected: Ty) { - let db = setup_db(); - - assert_eq!(ty.into_type(&db).repr(&db), expected.into_type(&db)); - } - #[test] fn typing_vs_typeshed_no_default() { let db = TestDbBuilder::new() diff --git a/crates/red_knot_vendored/knot_extensions/knot_extensions.pyi b/crates/red_knot_vendored/knot_extensions/knot_extensions.pyi index e2695caa97392..3b0f01eb65894 100644 --- a/crates/red_knot_vendored/knot_extensions/knot_extensions.pyi +++ b/crates/red_knot_vendored/knot_extensions/knot_extensions.pyi @@ -18,7 +18,7 @@ TypeOf: _SpecialForm # Ideally, these would be annotated using `TypeForm`, but that has not been # standardized yet (https://peps.python.org/pep-0747). def is_equivalent_to(type_a: Any, type_b: Any) -> bool: ... -def is_subtype_of(type_derived: Any, typ_ebase: Any) -> bool: ... +def is_subtype_of(type_derived: Any, type_base: Any) -> bool: ... def is_assignable_to(type_target: Any, type_source: Any) -> bool: ... def is_disjoint_from(type_a: Any, type_b: Any) -> bool: ... def is_fully_static(type: Any) -> bool: ...