Split out of #408. Confirmed live on main @ da74283 and on the bidirectionaltype-inference branch (that branch's only change to this file is routing is_assignable_to_bound through a SubtypingContext; this code is untouched).
The code
crates/basilisk-checker/src/rules/aliases_implicit.rs:693-698
// If all args are simple types (no `[...]` or `...`),
// the ParamSpec arg is probably wrong
let all_simple = args.iter().all(|arg| {
let trimmed = arg.trim();
!trimmed.contains('[') && trimmed != "..."
});
if all_simple && args.len() > 1 {
// → error: "Invalid type argument for `ParamSpec` parameter in `{base}`"
}
This emits a hard error on a shape guess. Its own comment says "probably".
Why it looks correct
The alias positions come from collect_typevar_bounds, which records TypeVar names in order of first textual appearance in the RHS. For the conformance suite's
GoodTypeAlias9 = Callable[Concatenate[int, P], R]
that yields [P, R], so GoodTypeAlias9[int, int] gives arg_count == typevar_count == 2, both args "simple", len > 1 → fires. That is aliases_implicit.py:80, and it is the only case the branch was ever exercised against.
It never identifies the ParamSpec position
The check never asks which argument corresponds to the ParamSpec. It asks whether all arguments look bracket-free. Consequences:
False negative — invalid ParamSpec argument accepted:
def f(x: GoodTypeAlias9[int, [str]]): ...
Position 0 is P (a ParamSpec) and receives int — invalid, it must be a parameter list or .... Position 1 is R (a TypeVar) and receives [str] — also invalid, that is not a type. Because "[str]" contains [, all_simple is false and the entire branch is skipped. Two errors, zero diagnostics.
False negative — nested annotations are never reached:
annotation_base_name splits on the first [ and check_single_annotation is called only from the two top-level loops (:606, :627) — it never recurses into type arguments. So:
def f(x: list[GoodTypeAlias2[int]]): ...
resolves the base name to list, finds no alias, and returns. The inner misuse — the same error the suite checks at top level on line 76 — is invisible at any nesting depth.
Fix
Determine parameter kinds from the alias's declared type parameters, match each argument to its position, and validate a ParamSpec argument as a parameter list, Concatenate[...], ..., or another ParamSpec. Recurse into type arguments so nested annotations are checked. Until that exists, this diagnostic should not be emitted — a check whose comment says "probably" must not produce an error.
Needs off-suite regression tests: both cases above are invisible to the conformance suite.
Split out of #408. Confirmed live on
main@da74283and on thebidirectionaltype-inferencebranch (that branch's only change to this file is routingis_assignable_to_boundthrough aSubtypingContext; this code is untouched).The code
crates/basilisk-checker/src/rules/aliases_implicit.rs:693-698This emits a hard error on a shape guess. Its own comment says "probably".
Why it looks correct
The alias positions come from
collect_typevar_bounds, which records TypeVar names in order of first textual appearance in the RHS. For the conformance suite'sthat yields
[P, R], soGoodTypeAlias9[int, int]givesarg_count == typevar_count == 2, both args "simple",len > 1→ fires. That isaliases_implicit.py:80, and it is the only case the branch was ever exercised against.It never identifies the ParamSpec position
The check never asks which argument corresponds to the ParamSpec. It asks whether all arguments look bracket-free. Consequences:
False negative — invalid ParamSpec argument accepted:
Position 0 is
P(a ParamSpec) and receivesint— invalid, it must be a parameter list or.... Position 1 isR(a TypeVar) and receives[str]— also invalid, that is not a type. Because"[str]"contains[,all_simpleisfalseand the entire branch is skipped. Two errors, zero diagnostics.False negative — nested annotations are never reached:
annotation_base_namesplits on the first[andcheck_single_annotationis called only from the two top-level loops (:606,:627) — it never recurses into type arguments. So:resolves the base name to
list, finds no alias, and returns. The inner misuse — the same error the suite checks at top level on line 76 — is invisible at any nesting depth.Fix
Determine parameter kinds from the alias's declared type parameters, match each argument to its position, and validate a ParamSpec argument as a parameter list,
Concatenate[...],..., or another ParamSpec. Recurse into type arguments so nested annotations are checked. Until that exists, this diagnostic should not be emitted — a check whose comment says "probably" must not produce an error.Needs off-suite regression tests: both cases above are invisible to the conformance suite.