Split out of #408. Confirmed live on main @ da74283 and on the bidirectionaltype-inference branch.
The code
crates/basilisk-checker/src/rules/aliases_implicit.rs:405-408, in build_alias_info_map's implicit-alias pass:
// Heuristic: if the name starts with uppercase and the RHS is a type
// expression (name, subscript, or union), treat it as an implicit alias
let first_char = var.name.chars().next().unwrap_or('a');
if !first_char.is_ascii_uppercase() {
continue;
}
A module-level assignment is recognised as an implicit type alias only if its name begins with an uppercase ASCII letter. Naming convention is load-bearing: it decides whether the binding enters alias_map at all, and therefore whether any downstream check sees it — parameterization, ParamSpec, bounds, and union instantiation.
Why it looks correct
The conformance file names every implicit alias GoodTypeAlias1…GoodTypeAlias13, ListAlias, ListOrSetAlias. Uppercase-first holds for all of them. PEP 613 and PEP 695 impose no such requirement — the typing spec says nothing about the case of an alias's name.
Failing cases
Union instantiation, silently accepted:
list_or_set = list | set
list_or_set() # should error: cannot instantiate a union alias
ListOrSetAlias() is caught (aliases_implicit.py:133); the identical construct with a lowercase name is not, because check_union_alias_instantiation looks the callee up in alias_map and the binding was never inserted.
Parameterization, silently accepted:
my_alias = list[int]
def f(x: my_alias[str]) -> None: ... # already specialized — should error
Also non-ASCII: Ålias = list | set fails is_ascii_uppercase and is skipped, so the rule is inconsistent for any identifier outside ASCII, which Python permits.
Secondary — looks_like_type_expression
The companion gate at :529 is a character blacklist over RHS text:
!text.contains(['=', '+', '-', '*', '/', '%', '!', '~', '^', '&', '{', '}'])
It rejects any RHS containing those characters anywhere, including inside a string forward reference or a Literal[...] argument, and accepts anything else. It is a second textual heuristic stacked on the first.
Fix
Decide alias-hood from binding information the resolver already has — the assignment's target, its RHS expression node, and whether the RHS is a type expression — not from the spelling of the name. Both gates should be replaced by structural analysis of the Ruff AST, matching the approach now used in aliases_type_statement.rs.
Needs off-suite regression tests: every implicit alias in the conformance suite is uppercase-first, so no suite case can detect this.
Split out of #408. Confirmed live on
main@da74283and on thebidirectionaltype-inferencebranch.The code
crates/basilisk-checker/src/rules/aliases_implicit.rs:405-408, inbuild_alias_info_map's implicit-alias pass:A module-level assignment is recognised as an implicit type alias only if its name begins with an uppercase ASCII letter. Naming convention is load-bearing: it decides whether the binding enters
alias_mapat all, and therefore whether any downstream check sees it — parameterization, ParamSpec, bounds, and union instantiation.Why it looks correct
The conformance file names every implicit alias
GoodTypeAlias1…GoodTypeAlias13,ListAlias,ListOrSetAlias. Uppercase-first holds for all of them. PEP 613 and PEP 695 impose no such requirement — the typing spec says nothing about the case of an alias's name.Failing cases
Union instantiation, silently accepted:
ListOrSetAlias()is caught (aliases_implicit.py:133); the identical construct with a lowercase name is not, becausecheck_union_alias_instantiationlooks the callee up inalias_mapand the binding was never inserted.Parameterization, silently accepted:
Also non-ASCII:
Ålias = list | setfailsis_ascii_uppercaseand is skipped, so the rule is inconsistent for any identifier outside ASCII, which Python permits.Secondary —
looks_like_type_expressionThe companion gate at
:529is a character blacklist over RHS text:It rejects any RHS containing those characters anywhere, including inside a string forward reference or a
Literal[...]argument, and accepts anything else. It is a second textual heuristic stacked on the first.Fix
Decide alias-hood from binding information the resolver already has — the assignment's target, its RHS expression node, and whether the RHS is a type expression — not from the spelling of the name. Both gates should be replaced by structural analysis of the Ruff AST, matching the approach now used in
aliases_type_statement.rs.Needs off-suite regression tests: every implicit alias in the conformance suite is uppercase-first, so no suite case can detect this.