Skip to content

Commit fd547c3

Browse files
olwangclaude
andcommitted
rsscript: generic type aliases expand at every comparison site
Type aliases were only resolved for `let` bindings, and only by exact name — so a non-generic alias used as a parameter type (`xs: read IntList`) and any generic alias (`Pair<Int>`) failed type comparison (RS0207). Add `Analyzer::expand_type_alias`: it expands an alias reference (generic or not) by looking up the alias root and substituting the reference's arguments for the alias's parameters (via text_util::substitute_type_args), chasing the chain with a depth bound. Applied at the call-argument, return, and binding comparison sites; generic aliases now resolve as param/return/binding types. Test: tests/fixtures/pass/type-alias-generic.rss (Pair<T>=Result<T,String>, IntList=List<Int> across all positions). lib/frontend/lowering green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent cc3634e commit fd547c3

4 files changed

Lines changed: 103 additions & 12 deletions

File tree

crates/rsscript/src/analyzer.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,22 @@ fn analyze_program(
178178
type_aliases.extend(type_aliases_from_program(interface));
179179
}
180180
type_aliases.extend(type_aliases_from_program(&syntax_program));
181+
let mut type_alias_params = std::collections::BTreeMap::new();
182+
for interface in builtin_interface_programs()
183+
.iter()
184+
.chain(interface_programs.iter())
185+
{
186+
type_alias_params.extend(type_alias_params_from_program(interface));
187+
}
188+
type_alias_params.extend(type_alias_params_from_program(&syntax_program));
181189
let mut analyzer = Analyzer {
182190
tokens: &tokens,
183191
syntax_program,
184192
interface_programs,
185193
hir,
186194
diagnostics: Vec::new(),
187195
type_aliases,
196+
type_alias_params,
188197
in_task_group: false,
189198
async_let_names: Vec::new(),
190199
};
@@ -207,6 +216,28 @@ fn type_aliases_from_program(
207216
})
208217
}
209218

219+
/// The generic parameter names of each type alias (`type Pair<T> = ...` → `T`),
220+
/// so generic aliases can be expanded by substituting arguments for parameters.
221+
fn type_alias_params_from_program(
222+
program: &crate::syntax::ast::Program,
223+
) -> impl Iterator<Item = (String, Vec<String>)> + '_ {
224+
use crate::syntax::ast::Item;
225+
program.items.iter().filter_map(|item| {
226+
if let Item::TypeAlias(alias) = item {
227+
Some((
228+
alias.name.clone(),
229+
alias
230+
.type_params
231+
.iter()
232+
.map(|param| param.name.clone())
233+
.collect(),
234+
))
235+
} else {
236+
None
237+
}
238+
})
239+
}
240+
210241
fn analyze_syntax_program(
211242
tokens: Vec<Token>,
212243
syntax_program: crate::syntax::ast::Program,
@@ -219,6 +250,7 @@ fn analyze_syntax_program(
219250
hir,
220251
diagnostics: Vec::new(),
221252
type_aliases: Default::default(),
253+
type_alias_params: Default::default(),
222254
in_task_group: false,
223255
async_let_names: Vec::new(),
224256
};
@@ -251,6 +283,9 @@ pub(crate) struct Analyzer<'a> {
251283
pub(crate) hir: Hir,
252284
pub(crate) diagnostics: Vec<Diagnostic>,
253285
pub(crate) type_aliases: std::collections::BTreeMap<String, String>,
286+
/// Type-alias name -> its generic parameter names (empty for non-generic
287+
/// aliases), used to expand generic aliases like `Pair<Int>`.
288+
pub(crate) type_alias_params: std::collections::BTreeMap<String, Vec<String>>,
254289
in_task_group: bool,
255290
pub(crate) async_let_names: Vec<String>,
256291
}
@@ -1039,6 +1074,40 @@ impl Analyzer<'_> {
10391074
current
10401075
}
10411076

1077+
/// Expand a type-alias reference, including generic aliases, to its target
1078+
/// type. `IntList` -> `List<Int>`; `Pair<Int>` -> `Result<Int, String>` for
1079+
/// `type Pair<T> = Result<T, String>`. Non-aliases pass through unchanged.
1080+
pub(crate) fn expand_type_alias(&self, type_name: &str) -> String {
1081+
use crate::text_util::{substitute_type_args, type_arg_names, type_root_name};
1082+
let mut current = type_name.trim().to_string();
1083+
for _ in 0..16 {
1084+
let root = type_root_name(&current);
1085+
let Some(target) = self.type_aliases.get(root) else {
1086+
break;
1087+
};
1088+
let params = self.type_alias_params.get(root).cloned().unwrap_or_default();
1089+
if params.is_empty() {
1090+
current = target.clone();
1091+
} else {
1092+
// Generic alias: substitute the reference's arguments for the
1093+
// alias's parameters. On arity mismatch, leave it for the normal
1094+
// type checks to report.
1095+
let Some(args) = type_arg_names(&current) else {
1096+
break;
1097+
};
1098+
if args.len() != params.len() {
1099+
break;
1100+
}
1101+
let subs: std::collections::HashMap<String, String> = params
1102+
.into_iter()
1103+
.zip(args.into_iter().map(str::to_string))
1104+
.collect();
1105+
current = substitute_type_args(target, &subs);
1106+
}
1107+
}
1108+
current
1109+
}
1110+
10421111
fn run(&mut self) {
10431112
self.check_single_feature_declaration();
10441113
self.check_unknown_file_features();

crates/rsscript/src/checks/calls.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -357,8 +357,8 @@ fn check_binding_type(
357357
if unresolved_generic_type(actual) {
358358
return;
359359
}
360-
let resolved_expected = analyzer.resolve_type_alias(expected).to_string();
361-
let resolved_actual = analyzer.resolve_type_alias(actual).to_string();
360+
let resolved_expected = analyzer.expand_type_alias(expected);
361+
let resolved_actual = analyzer.expand_type_alias(actual);
362362
if json_value_accepts_literal(&resolved_expected, value) {
363363
return;
364364
}
@@ -723,7 +723,10 @@ fn check_return_expr_type(
723723
if check_list_literal_type(analyzer, &expected, value, "return value") {
724724
return;
725725
}
726-
if !argument_type_matches(&expected, actual) {
726+
if !argument_type_matches(
727+
&analyzer.expand_type_alias(&expected),
728+
&analyzer.expand_type_alias(actual),
729+
) {
727730
return_type_mismatch_diagnostic(analyzer, &function.name, actual, &expected, span);
728731
}
729732
}
@@ -1344,7 +1347,10 @@ fn check_call_args(
13441347
if check_list_literal_type(analyzer, &expected_type, &arg.value, "argument") {
13451348
continue;
13461349
}
1347-
if !argument_type_matches(&expected_type, actual_type) {
1350+
if !argument_type_matches(
1351+
&analyzer.expand_type_alias(&expected_type),
1352+
&analyzer.expand_type_alias(actual_type),
1353+
) {
13481354
analyzer.diagnostics.push(
13491355
Diagnostic::error(
13501356
code::ARGUMENT_TYPE_MISMATCH,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Generic and non-generic type aliases expand at every type-comparison site —
2+
// parameter, return, and binding positions — including aliases that reference
3+
// imported/builtin generic types.
4+
5+
type Pair<T> = Result<T, String>
6+
type IntList = List<Int>
7+
8+
fn first_ok() -> fresh Pair<Int> {
9+
return Ok(1)
10+
}
11+
12+
fn count(xs: read IntList) -> Int {
13+
return List.len(list: read xs)
14+
}
15+
16+
fn main() -> Unit {
17+
let p: Pair<Int> = first_ok()
18+
let n: IntList = List.new<Int>()
19+
Log.write(message: read String.from_int(value: count(xs: read n)))
20+
return Unit
21+
}

docs/tinygrad-port-todo.md

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,12 @@ aliasing, qualified `module.fn`/`.Type`/value access, `use module.*` glob,
77
qualified variant patterns), pattern matching (variants/options/constants/tuples/
88
lists), tuples & destructuring, `#lower_name` escape hatch, source-qualified
99
symbol inventory, type-associated constants & static methods, value-semantics
10-
clone/derives, Option ergonomics (`?`-on-Option + combinators), and default
11-
parameters (Copy and non-Copy).
10+
clone/derives, Option ergonomics (`?`-on-Option + combinators), default
11+
parameters (Copy and non-Copy), and type aliases (generic + non-generic, expanded
12+
at every comparison site).
1213

1314
## Must unblock awkward valid ports
1415

15-
- [ ] **Generic type aliases.** Basic `type X = Y` aliases work; add *generic*
16-
aliases (`type Pair<T> = ...`) that can reference imported types and appear in
17-
the symbol inventory as type-level symbols, without creating value-namespace
18-
placeholders.
19-
_Why:_ Python `TypeVar`/generic-alias symbols otherwise become dummy constants.
20-
2116
- [ ] **Method/property lowering ergonomics.** Provide a simple way to model
2217
Python-style getter properties and method-like computed fields.
2318
_Why:_ tinygrad has many small property methods where the body is simple but the

0 commit comments

Comments
 (0)