diff --git a/crates/pyrefly_config/src/error.rs b/crates/pyrefly_config/src/error.rs index c0318cedd6..e77b2990ab 100644 --- a/crates/pyrefly_config/src/error.rs +++ b/crates/pyrefly_config/src/error.rs @@ -38,15 +38,15 @@ impl ErrorDisplayConfig { if let Some(&severity) = self.0.get(&kind) { return severity; } - if let Some(parent) = kind.parent_kind() { - if let Some(&severity) = self.0.get(&parent) { - return severity; - } + if let Some(parent) = kind.parent_kind() + && let Some(&severity) = self.0.get(&parent) + { + return severity; } - if let Some(alias) = kind.deprecated_alias() { - if let Some(&severity) = self.0.get(&alias) { - return severity; - } + if let Some(alias) = kind.deprecated_alias() + && let Some(&severity) = self.0.get(&alias) + { + return severity; } kind.default_severity() } diff --git a/pyrefly/lib/binding/expr.rs b/pyrefly/lib/binding/expr.rs index 53aeba2fa7..84b894d337 100644 --- a/pyrefly/lib/binding/expr.rs +++ b/pyrefly/lib/binding/expr.rs @@ -643,6 +643,68 @@ impl<'a> BindingsBuilder<'a> { self.check_private_attribute_usage(attr); self.ensure_expr(&mut attr.value, usage); } + Expr::Subscript(ExprSubscript { value, slice, .. }) => { + // Some subscripts are (or contain) type expressions even when they appear in a + // value context, e.g. `list["A | B"]([x])`. Ensure the slice is bound as a type so + // forward-reference strings are parsed and names inside are bound. + // + // Be careful about attribute access: `dict.__dict__` is an attribute on the class + // `dict` (not a module), and `dict.__dict__["fromkeys"]` is a runtime mappingproxy + // key lookup. Avoid treating those as "type-like subscripts". + let special_export = match &**value { + Expr::Name(_) => self.as_special_export(value), + Expr::Attribute(ExprAttribute { value: base, .. }) + if let Expr::Name(base_name) = &**base + && matches!( + self.scopes.flow_style_for_name(&base_name.id), + Some(FlowStyle::MergeableImport(_) | FlowStyle::ImportAs(_)) + ) => + { + self.as_special_export(value) + } + _ => None, + }; + + if let Some(special_export) = special_export + && matches!( + special_export, + SpecialExport::Union + | SpecialExport::Optional + | SpecialExport::Annotated + | SpecialExport::Callable + | SpecialExport::BuiltinsDict + | SpecialExport::TypingDict + | SpecialExport::BuiltinsList + | SpecialExport::TypingList + | SpecialExport::BuiltinsTuple + | SpecialExport::TypingTuple + | SpecialExport::BuiltinsType + | SpecialExport::TypingType + | SpecialExport::BuiltinsSet + | SpecialExport::BuiltinsFrozenset + | SpecialExport::TypingMapping + | SpecialExport::TypeForm + ) + { + self.ensure_expr(&mut *value, usage); + let mut type_usage = Usage::StaticTypeInformation; + if special_export == SpecialExport::Annotated + && let Expr::Tuple(tup) = &mut **slice + && !tup.is_empty() + { + // Only the first argument to Annotated[...] is a type; the rest are metadata. + self.ensure_type_impl(&mut tup.elts[0], &mut None, false, &mut type_usage); + for elt in tup.elts[1..].iter_mut() { + self.ensure_expr(elt, &mut Usage::StaticTypeInformation); + } + } else { + self.ensure_type_impl(&mut *slice, &mut None, false, &mut type_usage); + } + } else { + self.ensure_expr(&mut *value, usage); + self.ensure_expr(&mut *slice, usage); + } + } Expr::If(x) => { // Ternary operation. We treat it like an if/else statement. // Process the test before forking so walrus-defined names are diff --git a/pyrefly/lib/test/typeform.rs b/pyrefly/lib/test/typeform.rs index 9c20b0709f..99ea9586aa 100644 --- a/pyrefly/lib/test/typeform.rs +++ b/pyrefly/lib/test/typeform.rs @@ -129,3 +129,31 @@ import types v: types.UnionType = str | None "#, ); + +testcase!( + test_typeform_generic_alias_string_type_argument_in_value_context, + r#" +from __future__ import annotations + +from typing import assert_type +from typing import Annotated + +class Tomato: ... +class Cucumber: ... + +def main(t: Tomato) -> None: + a = list["Tomato | Cucumber"]([t]) + assert_type(a, list[Tomato | Cucumber]) + + b = set["Tomato | Cucumber"]([t]) + assert_type(b, set[Tomato | Cucumber]) + + c = frozenset["Tomato | Cucumber"]([t]) + assert_type(c, frozenset[Tomato | Cucumber]) + +x = Annotated[int, "meta"] + +# `dict.__dict__` is a runtime mappingproxy; string subscripting is a key lookup. +dict.__dict__["fromkeys"] + "#, +);