Description
When a type alias is defined using Union[] with string names (forward references), the type checker misreads those strings as plain text values instead of class names. This floods the output with false type errors.
Minimal Reproducible Example
import from typing { Union }
glob MyType = Union[('Foo', 'Bar')];
obj Foo {}
obj Bar {}
def make -> MyType {
return Foo(); # Should be fine — Foo is a valid member of MyType
}
Expected: No errors. Foo is a valid member of MyType.
Actual:
error[E1002]: Cannot return Foo, expected Literal["Foo"] | Literal["Bar"]
Root Cause
The type checker evaluates each string inside Union[(...)] using get_type_of_expression. When called on a plain string like 'Foo' outside a type-annotation position, it returns Literal["Foo"] (the text) — not the Foo class.
So Union[('Foo', 'Bar')] becomes Literal["Foo"] | Literal["Bar"] instead of Foo | Bar.
The function _try_resolve_forward_ref already exists to resolve string names to real types — it is used correctly for has x: 'Foo' and -> 'Foo' annotations — but it was never called inside the Union[] handler.
Real-World Impact
This pattern is used in doc_ir.jac:
glob DocType = Union[
('Doc', 'Text', 'Line', 'Group', 'Indent', 'Concat', 'IfBreak', 'Align')
];
Because DocType resolves to string literals instead of actual classes, every method in doc_ir_gen_pass.impl.jac that uses DocType as a return type or parameter type gets flagged:
error[E1002]: Cannot return Concat, expected Literal["Doc"] | Literal["Text"] | ...
error[E1053]: Cannot assign Text to parameter of type Literal["Doc"] | Literal["Text"] | ...
~1,440 false errors in a single file — all from one misread.
Description
When a type alias is defined using
Union[]with string names (forward references), the type checker misreads those strings as plain text values instead of class names. This floods the output with false type errors.Minimal Reproducible Example
Expected: No errors.
Foois a valid member ofMyType.Actual:
Root Cause
The type checker evaluates each string inside
Union[(...)]usingget_type_of_expression. When called on a plain string like'Foo'outside a type-annotation position, it returnsLiteral["Foo"](the text) — not theFooclass.So
Union[('Foo', 'Bar')]becomesLiteral["Foo"] | Literal["Bar"]instead ofFoo | Bar.The function
_try_resolve_forward_refalready exists to resolve string names to real types — it is used correctly forhas x: 'Foo'and-> 'Foo'annotations — but it was never called inside theUnion[]handler.Real-World Impact
This pattern is used in
doc_ir.jac:Because
DocTyperesolves to string literals instead of actual classes, every method indoc_ir_gen_pass.impl.jacthat usesDocTypeas a return type or parameter type gets flagged:~1,440 false errors in a single file — all from one misread.