From 7af42904ce59efe7cc61486ed3e36d99abfaeac5 Mon Sep 17 00:00:00 2001 From: Jingchen Ye <11172084+97littleleaf11@users.noreply.github.com> Date: Thu, 28 May 2026 23:54:43 +0800 Subject: [PATCH 1/2] Add context information for infer_literal_expr_type --- mypy/checker_shared.py | 4 +++- mypy/checkexpr.py | 28 ++++++++++++----------- mypy/checkpattern.py | 2 +- test-data/unit/check-columns.test | 18 +++++++++++++++ test-data/unit/plugins/literal_context.py | 21 +++++++++++++++++ 5 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 test-data/unit/plugins/literal_context.py diff --git a/mypy/checker_shared.py b/mypy/checker_shared.py index 2b25d3e73a6fe..ab210fe44a656 100644 --- a/mypy/checker_shared.py +++ b/mypy/checker_shared.py @@ -116,7 +116,9 @@ def visit_typeddict_index_expr( raise NotImplementedError @abstractmethod - def infer_literal_expr_type(self, value: LiteralValue, fallback_name: str) -> Type: + def infer_literal_expr_type( + self, value: LiteralValue, fallback_name: str, context: Context + ) -> Type: raise NotImplementedError @abstractmethod diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 5f1aeac8a7255..d98e5735ad6c1 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -454,7 +454,7 @@ def analyze_var_ref(self, var: Var, context: Context) -> Type: if self.is_literal_context() and var_type.last_known_value is not None: return var_type.last_known_value if var.name in {"True", "False"}: - return self.infer_literal_expr_type(var.name == "True", "builtins.bool") + return self.infer_literal_expr_type(var.name == "True", "builtins.bool", context) return var.type else: if not var.is_ready and self.chk.in_checked_function(): @@ -3503,7 +3503,9 @@ def analyze_external_member_access( def is_literal_context(self) -> bool: return is_literal_type_like(self.type_context[-1]) - def infer_literal_expr_type(self, value: LiteralValue, fallback_name: str) -> Type: + def infer_literal_expr_type( + self, value: LiteralValue, fallback_name: str, context: Context + ) -> Type: """Analyzes the given literal expression and determines if we should be inferring an Instance type, a Literal[...] type, or an Instance that remembers the original literal. We... @@ -3521,22 +3523,22 @@ def infer_literal_expr_type(self, value: LiteralValue, fallback_name: str) -> Ty the comments in Instance's constructor for more details. """ typ = self.named_type(fallback_name) + typ.set_line(context) + literal_typ = LiteralType( + value=value, fallback=typ, line=context.line, column=context.column + ) if self.is_literal_context(): - return LiteralType(value=value, fallback=typ) + return literal_typ else: if value is True: if self._literal_true is None: - self._literal_true = typ.copy_modified( - last_known_value=LiteralType(value=value, fallback=typ) - ) + self._literal_true = typ.copy_modified(last_known_value=literal_typ) return self._literal_true if value is False: if self._literal_false is None: - self._literal_false = typ.copy_modified( - last_known_value=LiteralType(value=value, fallback=typ) - ) + self._literal_false = typ.copy_modified(last_known_value=literal_typ) return self._literal_false - return typ.copy_modified(last_known_value=LiteralType(value=value, fallback=typ)) + return typ.copy_modified(last_known_value=literal_typ) def concat_tuples(self, left: TupleType, right: TupleType) -> TupleType: """Concatenate two fixed length tuples.""" @@ -3547,15 +3549,15 @@ def concat_tuples(self, left: TupleType, right: TupleType) -> TupleType: def visit_int_expr(self, e: IntExpr) -> Type: """Type check an integer literal (trivial).""" - return self.infer_literal_expr_type(e.value, "builtins.int") + return self.infer_literal_expr_type(e.value, "builtins.int", e) def visit_str_expr(self, e: StrExpr) -> Type: """Type check a string literal (trivial).""" - return self.infer_literal_expr_type(e.value, "builtins.str") + return self.infer_literal_expr_type(e.value, "builtins.str", e) def visit_bytes_expr(self, e: BytesExpr) -> Type: """Type check a bytes literal (trivial).""" - return self.infer_literal_expr_type(e.value, "builtins.bytes") + return self.infer_literal_expr_type(e.value, "builtins.bytes", e) def visit_float_expr(self, e: FloatExpr) -> Type: """Type check a float literal (trivial).""" diff --git a/mypy/checkpattern.py b/mypy/checkpattern.py index 53fa75fa5ec38..21b60d30764fd 100644 --- a/mypy/checkpattern.py +++ b/mypy/checkpattern.py @@ -219,7 +219,7 @@ def visit_singleton_pattern(self, o: SingletonPattern) -> PatternType: current_type = self.type_context[-1] value: bool | None = o.value if isinstance(value, bool): - typ = self.chk.expr_checker.infer_literal_expr_type(value, "builtins.bool") + typ = self.chk.expr_checker.infer_literal_expr_type(value, "builtins.bool", o) elif value is None: typ = NoneType() else: diff --git a/test-data/unit/check-columns.test b/test-data/unit/check-columns.test index dd8389c195583..ee33bb45ce4e6 100644 --- a/test-data/unit/check-columns.test +++ b/test-data/unit/check-columns.test @@ -306,6 +306,24 @@ if int(): if int(): reveal_type(1) # N:17: Revealed type is "Literal[1]?" +[case testColumnLiteralExprTypeContext] +# flags: --config-file tmp/mypy.ini +def print_literal_context(x: object) -> None: ... + +print_literal_context(1) +print_literal_context("x") +print_literal_context(True) +print_literal_context(False) + +[file mypy.ini] +\[mypy] +plugins=/test-data/unit/plugins/literal_context.py +[out] +main:4:23: note: literal type Literal[1]? has line and column context +main:5:23: note: literal type Literal['x']? has line and column context +main:6:23: note: literal type Literal[True]? has line and column context +main:7:23: note: literal type Literal[False]? has line and column context + [case testColumnNonOverlappingEqualityCheck] # flags: --strict-equality if 1 == '': # E:4: Non-overlapping equality check (left operand type: "Literal[1]", right operand type: "Literal['']") diff --git a/test-data/unit/plugins/literal_context.py b/test-data/unit/plugins/literal_context.py new file mode 100644 index 0000000000000..7ed84d513b033 --- /dev/null +++ b/test-data/unit/plugins/literal_context.py @@ -0,0 +1,21 @@ +from collections.abc import Callable + +from mypy.plugin import FunctionContext, Plugin +from mypy.types import Type + + +class LiteralContextPlugin(Plugin): + def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None: + if fullname == "__main__.print_literal_context": + return report_literal_type_context + return None + + +def report_literal_type_context(ctx: FunctionContext) -> Type: + typ = ctx.arg_types[0][0] + ctx.api.msg.note(f"literal type {typ} has line and column context", typ) + return ctx.default_return_type + + +def plugin(version: str) -> type[LiteralContextPlugin]: + return LiteralContextPlugin From 45ba9c6ec2d1fa02ed5c7e4f5dba199dd567d5a6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 16:00:35 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/checkexpr.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index d98e5735ad6c1..4c91fe944b76b 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -454,7 +454,9 @@ def analyze_var_ref(self, var: Var, context: Context) -> Type: if self.is_literal_context() and var_type.last_known_value is not None: return var_type.last_known_value if var.name in {"True", "False"}: - return self.infer_literal_expr_type(var.name == "True", "builtins.bool", context) + return self.infer_literal_expr_type( + var.name == "True", "builtins.bool", context + ) return var.type else: if not var.is_ready and self.chk.in_checked_function():