Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions crates/pyrefly_config/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
62 changes: 62 additions & 0 deletions pyrefly/lib/binding/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Comment thread
asukaminato0721 marked this conversation as resolved.
{
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
Expand Down
28 changes: 28 additions & 0 deletions pyrefly/lib/test/typeform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
"#,
);
Loading