Skip to content

Commit 08bcd6d

Browse files
olwangclaude
andcommitted
rsscript: reserve __ prefix for compiler-generated helpers
Generated helpers (`__TupleN`, `__rss_*` desugar temporaries, `__rsscript_*` runtime symbols) all use a `__` prefix but the namespace wasn't reserved, so a user declaration could collide. Reject user-declared function/type/sum/alias/ const names whose leaf begins with `__` (RS0015), exempting the synthetic `__TupleN` structs the tuple desugar injects. No real code uses `__` names. Test: fixtures/fail/reserved-double-underscore-name.rss; tuples still pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent fd547c3 commit 08bcd6d

3 files changed

Lines changed: 49 additions & 9 deletions

File tree

crates/rsscript/src/analyzer.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,13 @@ fn analyze_program(
203203
diagnostics
204204
}
205205

206+
/// `__TupleN` (N digits) is the synthetic tuple struct injected by the tuple
207+
/// desugar, not a user declaration, so it is exempt from the reserved-name rule.
208+
fn is_synthetic_tuple_name(name: &str) -> bool {
209+
name.strip_prefix("__Tuple")
210+
.is_some_and(|n| !n.is_empty() && n.bytes().all(|b| b.is_ascii_digit()))
211+
}
212+
206213
fn type_aliases_from_program(
207214
program: &crate::syntax::ast::Program,
208215
) -> impl Iterator<Item = (String, String)> + '_ {
@@ -1243,12 +1250,41 @@ impl Analyzer<'_> {
12431250
);
12441251
}
12451252
self.check_module_use_layout();
1253+
self.check_reserved_declaration_names();
12461254
let items = self.syntax_program.items.clone();
12471255
for item in &items {
12481256
self.check_unsupported_syntax_item(item);
12491257
}
12501258
}
12511259

1260+
/// Identifiers beginning with `__` are reserved for compiler-generated
1261+
/// symbols (tuple structs `__TupleN`, desugaring temporaries `__rss_*`,
1262+
/// runtime helpers `__rsscript_*`). Reject user declarations that claim a
1263+
/// reserved name so generated helpers can never collide with source symbols.
1264+
/// The synthetic `__TupleN` structs the tuple desugar injects are exempt.
1265+
fn check_reserved_declaration_names(&mut self) {
1266+
use crate::syntax::ast::Item;
1267+
for item in self.syntax_program.items.clone() {
1268+
let (name, span) = match &item {
1269+
Item::Function(decl) => (decl.name.as_str(), &decl.span),
1270+
Item::Type(decl) => (decl.name.as_str(), &decl.span),
1271+
Item::SumType(decl) => (decl.name.as_str(), &decl.span),
1272+
Item::TypeAlias(decl) => (decl.name.as_str(), &decl.span),
1273+
Item::Const(decl) => (decl.name.as_str(), &decl.span),
1274+
Item::Module(_) | Item::Use(_) => continue,
1275+
};
1276+
// `Type.method` reserves on the member, not the (user) type prefix.
1277+
let leaf = name.rsplit('.').next().unwrap_or(name);
1278+
if leaf.starts_with("__") && !is_synthetic_tuple_name(leaf) {
1279+
self.unsupported_syntax(
1280+
span.clone(),
1281+
"reserved declaration name",
1282+
"Identifiers beginning with `__` are reserved for compiler-generated symbols; rename this declaration.",
1283+
);
1284+
}
1285+
}
1286+
}
1287+
12521288
fn check_module_use_layout(&mut self) {
12531289
// Layout is per source file: a merged multi-file program legitimately has
12541290
// one `module` declaration per file, so the "at most one module" /
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// expect: RS0015
2+
// Identifiers beginning with `__` are reserved for compiler-generated symbols
3+
// (tuple structs, desugaring temporaries, runtime helpers).
4+
fn __helper() -> Int {
5+
return 1
6+
}
7+
8+
fn main() -> Unit {
9+
return Unit
10+
}

docs/tinygrad-port-todo.md

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ 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
1010
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).
11+
parameters (Copy and non-Copy), type aliases (generic + non-generic, expanded
12+
at every comparison site), and a reserved `__`-prefix namespace for
13+
compiler-generated helpers.
1314

1415
## Must unblock awkward valid ports
1516

@@ -60,13 +61,6 @@ at every comparison site).
6061
_Acceptance:_ a Rust backend error reports the RSS file, source span, source
6162
symbol, and lowered Rust symbol.
6263

63-
- [ ] **Generated/internal helper namespace.** Keep compiler-generated helpers
64-
compiler-reserved/mangled so they never collide with user module/type/member
65-
names.
66-
_Why:_ helper names should never consume source-level names or force a port to
67-
avoid otherwise valid identifiers. (Module isolation + `#lower_name` cover
68-
user-symbol collisions; this is the generated-helper side.)
69-
7064
- [ ] **External/FFI declaration ergonomics.** Declare copied runtime/autogen and
7165
device boundaries compactly — `native fn` exists, but make whole boundaries easy
7266
to bind without large wrapper files.

0 commit comments

Comments
 (0)