From 2c5941adb2b593cc8be25b4bcc7cbddee9275284 Mon Sep 17 00:00:00 2001 From: lyydsheep <2230561977@qq.com> Date: Sat, 1 Aug 2026 14:16:01 +0800 Subject: [PATCH] [pyrefly] Contextualize dict subscript literals for Any --- pyrefly/lib/alt/solve.rs | 45 ++++++++++++++++++++++++++++------- pyrefly/lib/test/inference.rs | 17 +++++++++++++ 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/pyrefly/lib/alt/solve.rs b/pyrefly/lib/alt/solve.rs index 75bf4cb8ad..7816ba557e 100644 --- a/pyrefly/lib/alt/solve.rs +++ b/pyrefly/lib/alt/solve.rs @@ -5477,6 +5477,21 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { ) -> (Type, Type) { let base = self.expr_infer(&subscript.value, errors); let slice_ty = self.expr_infer(&subscript.slice, errors); + // A declared dict[str, Any] target gives a dict-literal RHS a soft value hint. + let dict_value_hint = match &base { + Type::ClassType(cls) if cls.has_qname("builtins", "dict") => { + match cls.targs().as_slice() { + [key, value] + if matches!(key, Type::ClassType(cls) if cls.has_qname("builtins", "str")) + && value.is_any() => + { + Some(value.clone()) + } + _ => None, + } + } + _ => None, + }; let assigned_ty = self.distribute_over_union(&base, |base| { self.distribute_over_union(&slice_ty, |key| { match (base, key) { @@ -5525,15 +5540,27 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { }; match value { ExprOrBinding::Expr(e) => { - call_setitem(CallArg::expr(e)); - // We already emit errors for `e` during `call_method_or_error` - self.expr_infer( - e, - &ErrorCollector::new( - errors.module().clone(), - ErrorStyle::Never, - ), - ) + if let Some(hint) = &dict_value_hint + && matches!(e, Expr::Dict(_)) + { + let value_ty = self.expr_infer_with_hint( + e, + Some(HintRef::soft(hint)), + errors, + ); + call_setitem(CallArg::ty(&value_ty, e.range())); + value_ty + } else { + call_setitem(CallArg::expr(e)); + // We already emit errors for `e` during `call_method_or_error` + self.expr_infer( + e, + &ErrorCollector::new( + errors.module().clone(), + ErrorStyle::Never, + ), + ) + } } ExprOrBinding::Binding(b) => { let binding_ty = self diff --git a/pyrefly/lib/test/inference.rs b/pyrefly/lib/test/inference.rs index ab0e5a22b3..01395b3c76 100644 --- a/pyrefly/lib/test/inference.rs +++ b/pyrefly/lib/test/inference.rs @@ -195,6 +195,23 @@ def f(_symbols=[]): # E: Cannot infer type of empty container "#, ); +testcase!( + test_implicit_any_empty_container_in_typed_dict_subscript, + TestEnv::new().enable_implicit_any_error(), + r#" +from typing import Any + +response: dict[str, Any] = {} +response["meta"] = {"time_to_live": None} + +untyped = {} +untyped["meta"] = {"time_to_live": None} # E: Cannot infer type of empty container + +narrow: dict[str, int] = {} +narrow["meta"] = {"time_to_live": None} # E: Cannot set item in `dict[str, int]` # E: Cannot infer type of empty container +"#, +); + testcase!( test_implicit_any_default_disabled, r#"