rip-lang 3.16.3 → 3.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/docs/dist/rip.js +707 -251
- package/docs/dist/rip.min.js +191 -191
- package/docs/dist/rip.min.js.br +0 -0
- package/docs/extensions/vscode/print/print-1.0.15.vsix +0 -0
- package/docs/extensions/vscode/print/print-latest.vsix +0 -0
- package/docs/extensions/vscode/rip/rip-latest.vsix +0 -0
- package/docs/extensions/vscode/rip/vscode-rip-0.7.1.vsix +0 -0
- package/package.json +1 -1
- package/src/compiler.js +19 -7
- package/src/dts.js +22 -15
- package/src/lexer.js +30 -1
- package/src/typecheck.js +164 -22
- package/src/types.js +463 -2
package/src/types.js
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
// compiler.js (CodeEmitter.prototype.emitEnum) — that's real codegen,
|
|
13
13
|
// not type machinery.
|
|
14
14
|
|
|
15
|
+
import { RipError } from './error.js';
|
|
16
|
+
|
|
15
17
|
// ============================================================================
|
|
16
18
|
// installTypeSupport — adds rewriteTypes() to Lexer.prototype
|
|
17
19
|
// ============================================================================
|
|
@@ -35,6 +37,14 @@ export function installTypeSupport(Lexer) {
|
|
|
35
37
|
proto.rewriteTypes = function() {
|
|
36
38
|
let tokens = this.tokens;
|
|
37
39
|
let typeRefNames = this.typeRefNames = new Set();
|
|
40
|
+
|
|
41
|
+
// Rip 3.17 — single-colon type annotations. A `:` in a type-annotation
|
|
42
|
+
// slot (function params, return types, statement-level typed declarations)
|
|
43
|
+
// is reclassified to TYPE_ANNOTATION so it flows through the same machinery
|
|
44
|
+
// as the explicit `::`. Object properties, ternary branches, and nested
|
|
45
|
+
// colons are deliberately left untouched. Runs before the main type scan.
|
|
46
|
+
reclassifyColonTypes(tokens);
|
|
47
|
+
|
|
38
48
|
let gen = (tag, val, origin) => {
|
|
39
49
|
let t = [tag, val];
|
|
40
50
|
t.pre = 0;
|
|
@@ -88,7 +98,35 @@ export function installTypeSupport(Lexer) {
|
|
|
88
98
|
let prevToken = tokens[i - 1];
|
|
89
99
|
if (!prevToken) return 1;
|
|
90
100
|
|
|
91
|
-
|
|
101
|
+
// An arrow return type — `(params):: T -> body` / `(params):: T => body`
|
|
102
|
+
// — is uniquely flagged by a preceding `PARAM_END` (arrow param close).
|
|
103
|
+
// There the trailing `=>` is the arrow OPERATOR, not a TS function-type
|
|
104
|
+
// arrow, so the collector must stop at a depth-0 `=>` (it already stops
|
|
105
|
+
// at `->`). Without the flag it would eat the `=>` and the body as a
|
|
106
|
+
// function type, and the parser would never see the arrow.
|
|
107
|
+
let isArrowReturn = prevToken[0] === 'PARAM_END';
|
|
108
|
+
let typeTokens = collectTypeExpression(tokens, i + 1, { stopAtFatArrow: isArrowReturn });
|
|
109
|
+
|
|
110
|
+
// Foot-gun guard: a function-TYPE return must be parenthesized as a
|
|
111
|
+
// whole — `(x):: ((a: T) => R) => body`, not `(x):: (a: T) => R => body`.
|
|
112
|
+
// Unwrapped, the collector stops at the inner `=>` and the return type
|
|
113
|
+
// comes back as a bare parameter list `(a: T)` (a single `(…)` group
|
|
114
|
+
// with a top-level `:` , or empty `()`), which is never a valid type.
|
|
115
|
+
// Catch that exact shape and point at the fix rather than silently
|
|
116
|
+
// re-associating the body.
|
|
117
|
+
if (isArrowReturn && looksLikeBareFunctionType(typeTokens)) {
|
|
118
|
+
let loc = tokens[i + 1]?.loc;
|
|
119
|
+
throw new RipError('A function-type return on an arrow must be parenthesized', {
|
|
120
|
+
code: 'E_SYNTAX',
|
|
121
|
+
phase: 'lexer',
|
|
122
|
+
line: loc?.r ?? null,
|
|
123
|
+
column: loc?.c ?? null,
|
|
124
|
+
length: loc?.n ?? 1,
|
|
125
|
+
suggestion: 'wrap the whole function type in parens — ' +
|
|
126
|
+
'`(x):: ((a: T) => R) => body`, not `(x):: (a: T) => R => body`',
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
92
130
|
let typeStr = buildTypeString(typeTokens);
|
|
93
131
|
|
|
94
132
|
// Find the token that survives into the s-expression
|
|
@@ -312,8 +350,425 @@ export function installTypeSupport(Lexer) {
|
|
|
312
350
|
// Type expression collection helpers
|
|
313
351
|
// ============================================================================
|
|
314
352
|
|
|
353
|
+
// Does a collected arrow-return type look like a bare (un-parenthesized)
|
|
354
|
+
// function type — i.e. a single balanced `(…)` group spanning the whole type
|
|
355
|
+
// whose top level is a parameter list (an empty `()`, or a depth-1 `:`)? Such
|
|
356
|
+
// a "type" is only valid as the params of a function type, so seeing it in
|
|
357
|
+
// return position means the user wrote `(x):: (a: T) => R => body` and forgot
|
|
358
|
+
// to wrap the function type: `(x):: ((a: T) => R) => body`.
|
|
359
|
+
function looksLikeBareFunctionType(typeTokens) {
|
|
360
|
+
const isOpen = t => t === '(' || t === 'PARAM_START' || t === 'CALL_START';
|
|
361
|
+
const isClose = t => t === ')' || t === 'PARAM_END' || t === 'CALL_END';
|
|
362
|
+
if (typeTokens.length < 2 || !isOpen(typeTokens[0][0])) return false;
|
|
363
|
+
|
|
364
|
+
// The opening paren must match the LAST token: one group spanning the type.
|
|
365
|
+
let depth = 0;
|
|
366
|
+
for (let k = 0; k < typeTokens.length; k++) {
|
|
367
|
+
const tag = typeTokens[k][0];
|
|
368
|
+
if (isOpen(tag)) depth++;
|
|
369
|
+
else if (isClose(tag)) {
|
|
370
|
+
if (--depth === 0 && k !== typeTokens.length - 1) return false;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (depth !== 0) return false;
|
|
374
|
+
|
|
375
|
+
if (typeTokens.length === 2) return true; // empty `()`
|
|
376
|
+
|
|
377
|
+
// A `:` directly inside the outer parens (depth 1) ⇒ parameter-list shape.
|
|
378
|
+
// Brackets/braces nested inside don't count (e.g. `(string | number)` and the
|
|
379
|
+
// object literal `({ a: number })` have no depth-1 `:`). One more exclusion:
|
|
380
|
+
// a parenthesized CONDITIONAL type `(T extends U ? A : B)` has a depth-1 `:`,
|
|
381
|
+
// but it's the conditional's else-branch, always paired with a preceding `?`
|
|
382
|
+
// (TERNARY) at the same depth — a parameter `:` never is. Track that so a
|
|
383
|
+
// conditional return type isn't mistaken for a bare parameter list.
|
|
384
|
+
let d = 0, pendingTernary = false;
|
|
385
|
+
for (const t of typeTokens) {
|
|
386
|
+
const tag = t[0];
|
|
387
|
+
if (isOpen(tag) || tag === '[' || tag === '{' || tag === 'INDEX_START') d++;
|
|
388
|
+
else if (isClose(tag) || tag === ']' || tag === '}' || tag === 'INDEX_END') d--;
|
|
389
|
+
else if (d === 1 && tag === 'TERNARY') pendingTernary = true;
|
|
390
|
+
else if (d === 1 && tag === ':') {
|
|
391
|
+
if (pendingTernary) pendingTernary = false; // conditional else-branch
|
|
392
|
+
else return true; // parameter-list colon
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Rip 3.17 — does the token slice [a, b) form a complete, well-formed *type*
|
|
399
|
+
// expression (vs a value expression)? Used to decide whether a single `:` in a
|
|
400
|
+
// bare decl (`r: R`) or a function-type-valued decl (`get: (p) => void = …`) is
|
|
401
|
+
// a type annotation. A whitelist of type tokens + bracket/generic balance + an
|
|
402
|
+
// adjacency rule (two atoms with no separator is not a type — kills the value
|
|
403
|
+
// comparison `x < y > z`). Anything outside the whitelist (a CALL_START, `->`,
|
|
404
|
+
// `new`/UNARY, arithmetic, `&&`, `==`, …) marks it as a value, so the `:` is
|
|
405
|
+
// left alone and any side effects survive.
|
|
406
|
+
function isCompleteTypeExpr(tokens, a, b) {
|
|
407
|
+
if (b <= a) return false;
|
|
408
|
+
const SEP = new Set(['|', '&', ',', ':', '?', '.', '...', '=>']);
|
|
409
|
+
let par = 0, brk = 0, brc = 0, gen = 0, atomEnd = false;
|
|
410
|
+
for (let j = a; j < b; j++) {
|
|
411
|
+
const t = tokens[j][0], v = tokens[j][1];
|
|
412
|
+
if (t === '(' || t === 'PARAM_START') { par++; atomEnd = false; continue; }
|
|
413
|
+
if (t === ')' || t === 'PARAM_END') { if (--par < 0) return false; atomEnd = true; continue; }
|
|
414
|
+
if (t === '[' || t === 'INDEX_START') { brk++; atomEnd = false; continue; }
|
|
415
|
+
if (t === ']' || t === 'INDEX_END') { if (--brk < 0) return false; atomEnd = true; continue; }
|
|
416
|
+
if (t === '{') { brc++; atomEnd = false; continue; }
|
|
417
|
+
if (t === '}') { if (--brc < 0) return false; atomEnd = true; continue; }
|
|
418
|
+
if (t === 'COMPARE') {
|
|
419
|
+
if (v === '<') { gen++; atomEnd = false; continue; }
|
|
420
|
+
if (v === '>') { if (gen <= 0) return false; gen--; atomEnd = true; continue; }
|
|
421
|
+
return false; // ==, !=, <=, >= → not a type
|
|
422
|
+
}
|
|
423
|
+
if (t === 'SHIFT') {
|
|
424
|
+
if (v === '>>') { if (gen < 2) return false; gen -= 2; atomEnd = true; continue; }
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
if (t === '=') { if (gen > 0) { atomEnd = false; continue; } return false; } // generic default only
|
|
428
|
+
if (SEP.has(t)) { atomEnd = false; continue; }
|
|
429
|
+
if (t === 'IDENTIFIER' || t === 'PROPERTY' || t === 'NUMBER' ||
|
|
430
|
+
t === 'STRING' || t === 'NULL' || t === 'UNDEFINED' || t === 'BOOL') {
|
|
431
|
+
if (atomEnd) return false; // two atoms, no separator → not a type
|
|
432
|
+
atomEnd = true; continue;
|
|
433
|
+
}
|
|
434
|
+
return false; // any non-type token → value
|
|
435
|
+
}
|
|
436
|
+
return par === 0 && brk === 0 && brc === 0 && gen === 0 && atomEnd;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Rip 3.17 — positive evidence that a bare `name: T` is a typed forward
|
|
440
|
+
// declaration: the same identifier is assigned (`name = …`, `:=`, etc.)
|
|
441
|
+
// somewhere later in the same block (any nesting depth before the block's
|
|
442
|
+
// OUTDENT). Distinguishes `r: R` (then `r = …`) from a discarded object
|
|
443
|
+
// property. Starts scanning at `start` (just past the decl line's TERMINATOR).
|
|
444
|
+
function assignedLaterInBlock(tokens, start, name) {
|
|
445
|
+
const ASSIGN = new Set(['=', 'REACTIVE_ASSIGN', 'COMPUTED_ASSIGN', 'READONLY_ASSIGN']);
|
|
446
|
+
// Descend into control-flow sub-blocks (try/if/for — the medlabs `r = …`
|
|
447
|
+
// sits inside a `try`), but SKIP nested function bodies: an assignment inside
|
|
448
|
+
// a closure (`init -> … name = …`) is a different scope and must not count
|
|
449
|
+
// as evidence. A closure body is an INDENT immediately preceded by `->`/`=>`.
|
|
450
|
+
let indent = 0, bracket = 0, closureDepth = 0;
|
|
451
|
+
const closureAt = [];
|
|
452
|
+
for (let j = start; j < tokens.length; j++) {
|
|
453
|
+
const g = tokens[j][0];
|
|
454
|
+
if (g === 'INDENT') {
|
|
455
|
+
indent++;
|
|
456
|
+
const p = tokens[j - 1]?.[0];
|
|
457
|
+
if (p === '->' || p === '=>') { closureAt.push(indent); closureDepth++; }
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
if (g === 'OUTDENT') {
|
|
461
|
+
if (indent === 0) return false; // left the starting block
|
|
462
|
+
if (closureAt[closureAt.length - 1] === indent) { closureAt.pop(); closureDepth--; }
|
|
463
|
+
indent--; continue;
|
|
464
|
+
}
|
|
465
|
+
if (g === '(' || g === 'CALL_START' || g === 'PARAM_START' ||
|
|
466
|
+
g === '[' || g === 'INDEX_START' || g === '{') { bracket++; continue; }
|
|
467
|
+
if (g === ')' || g === 'CALL_END' || g === 'PARAM_END' ||
|
|
468
|
+
g === ']' || g === 'INDEX_END' || g === '}') { bracket--; continue; }
|
|
469
|
+
if (closureDepth === 0 && bracket === 0 && (g === 'IDENTIFIER' || g === 'PROPERTY') &&
|
|
470
|
+
tokens[j][1] === name && ASSIGN.has(tokens[j + 1]?.[0])) return true;
|
|
471
|
+
}
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Rip 3.17 — reclassify single-colon `:` into TYPE_ANNOTATION in the contexts
|
|
476
|
+
// where it means a type, so `:` and `::` share one code path. Scoped tightly:
|
|
477
|
+
//
|
|
478
|
+
// - function parameters: `(x: T)`, `(x: T = d)` (arrow PARAM_START + def)
|
|
479
|
+
// - return types: `(…): T ->`, `def f(…): T`
|
|
480
|
+
// - statement decls: `x: T = v` (binding name at statement start)
|
|
481
|
+
//
|
|
482
|
+
// Everything else keeps `:` as-is: object properties (`{x: 1}`, `foo a: 1`),
|
|
483
|
+
// implicit-object call args (`foo(a: 1)`), ternary (`a ? b : c`), and any colon
|
|
484
|
+
// nested inside `{}`/`[]`/`<>` (object-type interiors, generic args). The
|
|
485
|
+
// preceding object-key `PROPERTY` token is retagged back to `IDENTIFIER` so the
|
|
486
|
+
// downstream `::` handler treats it as a binder, not a property.
|
|
487
|
+
//
|
|
488
|
+
// Frame state is per parameter segment: a `:` is a param type only before any
|
|
489
|
+
// top-level `=` (so a ternary in a default `(x = a ? b : c)` is untouched) and
|
|
490
|
+
// only the first colon in the segment.
|
|
491
|
+
function reclassifyColonTypes(tokens) {
|
|
492
|
+
const isOpen = t => t === '(' || t === 'CALL_START' || t === 'PARAM_START' ||
|
|
493
|
+
t === '{' || t === '[' || t === 'INDEX_START';
|
|
494
|
+
const isClose = t => t === ')' || t === 'CALL_END' || t === 'PARAM_END' ||
|
|
495
|
+
t === '}' || t === ']' || t === 'INDEX_END';
|
|
496
|
+
|
|
497
|
+
// Is this CALL_START the param list of a `def`? Matches `DEF IDENT (` and
|
|
498
|
+
// `DEF IDENT <generics> (`. Deliberately narrow so an ordinary call
|
|
499
|
+
// `foo(a: 1)` (no preceding DEF) is never mistaken for a def param list.
|
|
500
|
+
const isDefParamStart = (i) => {
|
|
501
|
+
let j = i - 1;
|
|
502
|
+
// Skip a balanced generic list `<…>` between the def name and `(`.
|
|
503
|
+
const g0 = tokens[j]?.[0], v0 = tokens[j]?.[1];
|
|
504
|
+
if ((g0 === 'COMPARE' && v0 === '>') || (g0 === 'SHIFT' && v0 === '>>')) {
|
|
505
|
+
let depth = g0 === 'SHIFT' ? 2 : 1;
|
|
506
|
+
j--;
|
|
507
|
+
while (j >= 0 && depth > 0) {
|
|
508
|
+
const g = tokens[j][0], v = tokens[j][1];
|
|
509
|
+
if (g === 'COMPARE' && v === '>') depth++;
|
|
510
|
+
else if (g === 'SHIFT' && v === '>>') depth += 2;
|
|
511
|
+
else if (g === 'COMPARE' && v === '<') depth--;
|
|
512
|
+
else if (g === 'SHIFT' && v === '<<') depth -= 2;
|
|
513
|
+
j--;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (tokens[j]?.[0] !== 'IDENTIFIER' && tokens[j]?.[0] !== 'PROPERTY') return false;
|
|
517
|
+
return tokens[j - 1]?.[0] === 'DEF';
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
// Statement-level typed declaration `name: T = v` (and `:=`, `~=`, `=!`,
|
|
521
|
+
// `~>`, `<~`). True only when an assignment/binding operator appears at the
|
|
522
|
+
// top level BEFORE any function arrow — so a method/value like
|
|
523
|
+
// `name: (x) -> …` or `name: (x) => y = x` (arrow first) stays a binding,
|
|
524
|
+
// and a bare `name: T` (no binding op) stays an object property.
|
|
525
|
+
const STMT_START = new Set(['TERMINATOR', 'INDENT', 'OUTDENT', 'EXPORT']);
|
|
526
|
+
// Assignment/binding operators that mark `name: T <op> …` as a typed
|
|
527
|
+
// declaration. EFFECT (`~>`) and GATE (`<~`) are deliberately excluded: a
|
|
528
|
+
// bare `name: ~> …` is a schema computed getter (no type), so treating `~>`
|
|
529
|
+
// as a binding op would misread schema bodies as typed declarations.
|
|
530
|
+
const BINDING_OPS = new Set(['=', 'REACTIVE_ASSIGN', 'COMPUTED_ASSIGN', 'READONLY_ASSIGN']);
|
|
531
|
+
const atStatementStart = (t) => !t || STMT_START.has(t[0]);
|
|
532
|
+
const declBindsBeforeArrow = (start) => {
|
|
533
|
+
let depth = 0;
|
|
534
|
+
for (let j = start; j < tokens.length; j++) {
|
|
535
|
+
const g = tokens[j][0];
|
|
536
|
+
if (isOpen(g)) depth++;
|
|
537
|
+
else if (isClose(g)) depth--;
|
|
538
|
+
else if (depth === 0) {
|
|
539
|
+
if (g === '->' || g === '=>') return false;
|
|
540
|
+
if (BINDING_OPS.has(g)) return true;
|
|
541
|
+
// Typed effect/gate: `name: T ~> …` — an EFFECT/GATE that is NOT the
|
|
542
|
+
// first token after `:` follows a type (a bare `name: ~> …` schema
|
|
543
|
+
// getter has it first, so j === start, and stays excluded).
|
|
544
|
+
if ((g === 'EFFECT' || g === 'GATE') && j > start) return true;
|
|
545
|
+
if (g === 'TERMINATOR' || g === 'INDENT' || g === 'OUTDENT') return false;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return false;
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
// Does the value after a class-member `:` look like a function (a method),
|
|
552
|
+
// i.e. does a `->`/`=>` arrow appear at depth 0 before the member ends? If so
|
|
553
|
+
// it's a method binding, not a typed field — leave it alone.
|
|
554
|
+
const valueIsMethod = (start) => {
|
|
555
|
+
let depth = 0;
|
|
556
|
+
for (let j = start; j < tokens.length; j++) {
|
|
557
|
+
const g = tokens[j][0];
|
|
558
|
+
if (isOpen(g)) depth++;
|
|
559
|
+
else if (isClose(g)) depth--;
|
|
560
|
+
else if (depth === 0) {
|
|
561
|
+
if (g === '->' || g === '=>') return true;
|
|
562
|
+
if (g === 'TERMINATOR' || g === 'INDENT' || g === 'OUTDENT') return false;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return false;
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
// Track class/component bodies via INDENT/OUTDENT so a bare `name: T` member
|
|
569
|
+
// (or `@name: T` component prop) is read as a typed field. `pendingBody` is
|
|
570
|
+
// armed by `class`/`component` and consumed by the next INDENT (the body);
|
|
571
|
+
// methods inside it open their own (non-body) INDENT, so a member is in a
|
|
572
|
+
// typed body only when the innermost indent frame is that body.
|
|
573
|
+
let pendingBody = false;
|
|
574
|
+
const indentStack = [];
|
|
575
|
+
const inTypedBody = () => indentStack.length > 0 && indentStack[indentStack.length - 1];
|
|
576
|
+
|
|
577
|
+
// The binding name for a statement/field annotation is `tokens[i-1]` (a
|
|
578
|
+
// PROPERTY/IDENTIFIER), optionally preceded by `@` (component prop / promoted
|
|
579
|
+
// field). Returns true when that name sits at a statement boundary.
|
|
580
|
+
const nameAtStatementStart = (i) => {
|
|
581
|
+
const p = tokens[i - 1]?.[0];
|
|
582
|
+
if (p !== 'PROPERTY' && p !== 'IDENTIFIER') return false;
|
|
583
|
+
if (tokens[i - 2]?.[0] === '@') return atStatementStart(tokens[i - 3]);
|
|
584
|
+
return atStatementStart(tokens[i - 2]);
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
// A type expression (opened by any `::` / TYPE_ANNOTATION, or by a `:` we
|
|
588
|
+
// reclassify) must never have its interior touched: a function type like
|
|
589
|
+
// `{ cb: (r: R) => void }` carries single `:` colons that are TS member/param
|
|
590
|
+
// separators, not Rip annotations. `inType` suppresses reclassification until
|
|
591
|
+
// the type ends (a terminator at its start depth, or exiting that depth).
|
|
592
|
+
let inType = false, typeStartDepth = 0;
|
|
593
|
+
const enterType = () => { inType = true; typeStartDepth = stack.length; };
|
|
594
|
+
|
|
595
|
+
// Object-context detection for context D: a statement-start `name:` line that
|
|
596
|
+
// sits next to another `key:` line is an object member (`{a: A, b: B}`), not a
|
|
597
|
+
// bare typed declaration. `curLineKV` marks the current line as starting
|
|
598
|
+
// `name:`; committed to `prevSiblingKV` at the line's TERMINATOR; both reset
|
|
599
|
+
// at block boundaries.
|
|
600
|
+
let prevSiblingKV = false, curLineKV = false;
|
|
601
|
+
|
|
602
|
+
const stack = [];
|
|
603
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
604
|
+
const tag = tokens[i][0];
|
|
605
|
+
|
|
606
|
+
// Class-body tracking (INDENT/OUTDENT). `class` arms the next INDENT as a
|
|
607
|
+
// class body; nested method/blocks push non-class indents. INDENT/OUTDENT
|
|
608
|
+
// at the type's start depth also end a type expression.
|
|
609
|
+
if (tag === 'CLASS' || tag === 'COMPONENT') { pendingBody = true; continue; }
|
|
610
|
+
if (tag === 'INDENT') { indentStack.push(pendingBody); pendingBody = false; prevSiblingKV = curLineKV = false; if (inType && stack.length <= typeStartDepth) inType = false; continue; }
|
|
611
|
+
if (tag === 'OUTDENT') { indentStack.pop(); prevSiblingKV = curLineKV = false; if (inType && stack.length <= typeStartDepth) inType = false; continue; }
|
|
612
|
+
|
|
613
|
+
// Brackets are always tracked (depth drives the type-context boundary).
|
|
614
|
+
// Inside a type, an opener is never a real parameter list (it's a
|
|
615
|
+
// function-type's params), so it pushes a plain frame.
|
|
616
|
+
if (isOpen(tag)) {
|
|
617
|
+
let kind = 'other';
|
|
618
|
+
if (!inType) {
|
|
619
|
+
if (tag === 'PARAM_START') kind = 'param';
|
|
620
|
+
else if ((tag === 'CALL_START' || tag === '(') && isDefParamStart(i)) kind = 'defparam';
|
|
621
|
+
}
|
|
622
|
+
stack.push({ kind, sawEq: false, sawType: false });
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
if (isClose(tag)) {
|
|
626
|
+
const f = stack.pop();
|
|
627
|
+
if (f?.kind === 'defparam') { (tokens[i].data ??= {}).isDefParamEnd = true; }
|
|
628
|
+
if (inType && stack.length < typeStartDepth) inType = false;
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// A type annotation token (pre-existing `::`, or one created below) opens a
|
|
633
|
+
// type expression.
|
|
634
|
+
if (tag === 'TYPE_ANNOTATION') { if (!inType) enterType(); continue; }
|
|
635
|
+
|
|
636
|
+
// Inside a type: clear at the type's terminator (at its start depth);
|
|
637
|
+
// otherwise skip — never reclassify a colon that belongs to a type. `=>`
|
|
638
|
+
// does not terminate (it's a function-type arrow).
|
|
639
|
+
if (inType) {
|
|
640
|
+
if (stack.length <= typeStartDepth &&
|
|
641
|
+
(tag === '=' || tag === ',' || tag === 'TERMINATOR' || tag === '->' ||
|
|
642
|
+
BINDING_OPS.has(tag))) {
|
|
643
|
+
inType = false; // fall through; a terminator is never a `:` to reclassify
|
|
644
|
+
} else {
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Commit per-line key:value state at statement boundaries (depth 0).
|
|
650
|
+
if (tag === 'TERMINATOR' && stack.length === 0) { prevSiblingKV = curLineKV; curLineKV = false; }
|
|
651
|
+
|
|
652
|
+
if (tag === ':') {
|
|
653
|
+
const prev = tokens[i - 1];
|
|
654
|
+
const prevTag = prev?.[0];
|
|
655
|
+
// Mark this line as `name:`-shaped (object-member candidate) for D's
|
|
656
|
+
// adjacent-key detection.
|
|
657
|
+
if (stack.length === 0 && (prevTag === 'IDENTIFIER' || prevTag === 'PROPERTY') &&
|
|
658
|
+
atStatementStart(tokens[i - 2])) curLineKV = true;
|
|
659
|
+
// Return type: a `:` immediately after a closed param list. Arrow param
|
|
660
|
+
// lists close as PARAM_END; def param lists close as `)` or CALL_END
|
|
661
|
+
// (flagged `isDefParamEnd`).
|
|
662
|
+
if (prevTag === 'PARAM_END' ||
|
|
663
|
+
((prevTag === ')' || prevTag === 'CALL_END') && prev.data?.isDefParamEnd)) {
|
|
664
|
+
tokens[i][0] = 'TYPE_ANNOTATION'; enterType(); continue;
|
|
665
|
+
}
|
|
666
|
+
// Parameterless def return type: `def foo: T`.
|
|
667
|
+
if ((prevTag === 'IDENTIFIER' || prevTag === 'PROPERTY') &&
|
|
668
|
+
tokens[i - 2]?.[0] === 'DEF') {
|
|
669
|
+
if (prevTag === 'PROPERTY') prev[0] = 'IDENTIFIER';
|
|
670
|
+
tokens[i][0] = 'TYPE_ANNOTATION'; enterType(); continue;
|
|
671
|
+
}
|
|
672
|
+
// Statement-level typed declaration (top level, name at statement start,
|
|
673
|
+
// binding operator before any arrow). Covers `@name: T := v` props too.
|
|
674
|
+
// An `@`-prefixed name stays PROPERTY (the `@`-prop / promoted-field
|
|
675
|
+
// machinery needs it); a plain name is retagged IDENTIFIER like `::`.
|
|
676
|
+
if (stack.length === 0 && nameAtStatementStart(i) &&
|
|
677
|
+
declBindsBeforeArrow(i + 1)) {
|
|
678
|
+
if (prevTag === 'PROPERTY' && tokens[i - 2]?.[0] !== '@') prev[0] = 'IDENTIFIER';
|
|
679
|
+
tokens[i][0] = 'TYPE_ANNOTATION'; enterType(); continue;
|
|
680
|
+
}
|
|
681
|
+
// Context B — function-type-valued declaration: `name: (…) => Ret = value`
|
|
682
|
+
// (statement start). The type carries a `=>` arrow, so the binding-before-
|
|
683
|
+
// arrow check above misses it. Scan for the first bracket-depth-0 `=`
|
|
684
|
+
// (only ()[]{} count) whose preceding slice is a complete type expression
|
|
685
|
+
// — the `=>` is then a function-TYPE arrow. Try-and-continue handles
|
|
686
|
+
// generic defaults `Foo<T = U>` (the inner `=` leaves `Foo<T` incomplete).
|
|
687
|
+
if (stack.length === 0 && nameAtStatementStart(i)) {
|
|
688
|
+
let depth = 0;
|
|
689
|
+
for (let j = i + 1; j < tokens.length; j++) {
|
|
690
|
+
const g = tokens[j][0];
|
|
691
|
+
if (isOpen(g)) depth++;
|
|
692
|
+
else if (isClose(g)) { if (depth === 0) break; depth--; }
|
|
693
|
+
else if (depth === 0) {
|
|
694
|
+
if (g === 'TERMINATOR' || g === 'INDENT' || g === 'OUTDENT') break;
|
|
695
|
+
if (g === '=' && isCompleteTypeExpr(tokens, i + 1, j)) {
|
|
696
|
+
if (prevTag === 'PROPERTY' && tokens[i - 2]?.[0] !== '@') prev[0] = 'IDENTIFIER';
|
|
697
|
+
tokens[i][0] = 'TYPE_ANNOTATION'; enterType(); break;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
if (tokens[i][0] === 'TYPE_ANNOTATION') continue;
|
|
702
|
+
}
|
|
703
|
+
// Class/component-body typed field: a bare `name: T` (or `@name: T`)
|
|
704
|
+
// member with no initializer is a typed field unless its value is a
|
|
705
|
+
// method (`name: (…) -> …`).
|
|
706
|
+
if (inTypedBody() && stack.length === 0 && nameAtStatementStart(i) &&
|
|
707
|
+
!valueIsMethod(i + 1)) {
|
|
708
|
+
if (prevTag === 'PROPERTY' && tokens[i - 2]?.[0] !== '@') prev[0] = 'IDENTIFIER';
|
|
709
|
+
tokens[i][0] = 'TYPE_ANNOTATION'; enterType(); continue;
|
|
710
|
+
}
|
|
711
|
+
// Context D — bare typed declaration, no initializer: `name: T` at
|
|
712
|
+
// statement start, the line is NON-TAIL (not the last expression of its
|
|
713
|
+
// block — that would be an implicit-return object), the type slice is a
|
|
714
|
+
// complete type expression (rejects `name: build()` so side effects
|
|
715
|
+
// survive), and the same `name` is assigned later in the block (positive
|
|
716
|
+
// evidence it's a forward declaration, not a discarded object property).
|
|
717
|
+
if (stack.length === 0 && nameAtStatementStart(i) && tokens[i - 2]?.[0] !== '@') {
|
|
718
|
+
let depth = 0, end = -1;
|
|
719
|
+
for (let j = i + 1; j < tokens.length; j++) {
|
|
720
|
+
const g = tokens[j][0];
|
|
721
|
+
if (isOpen(g)) depth++;
|
|
722
|
+
else if (isClose(g)) { if (depth === 0) break; depth--; }
|
|
723
|
+
else if (depth === 0) {
|
|
724
|
+
if (g === 'TERMINATOR') { end = j; break; }
|
|
725
|
+
if (g === 'INDENT' || g === 'OUTDENT' || g === '=' || BINDING_OPS.has(g)) break;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
const nk = tokens[end + 1];
|
|
729
|
+
const nextKV = nk && (nk[0] === 'IDENTIFIER' || nk[0] === 'PROPERTY') &&
|
|
730
|
+
tokens[end + 2]?.[0] === ':';
|
|
731
|
+
if (end > i + 1 && nk && nk[0] !== 'OUTDENT' &&
|
|
732
|
+
!prevSiblingKV && !nextKV &&
|
|
733
|
+
isCompleteTypeExpr(tokens, i + 1, end) &&
|
|
734
|
+
assignedLaterInBlock(tokens, end + 1, prev[1])) {
|
|
735
|
+
if (prevTag === 'PROPERTY') prev[0] = 'IDENTIFIER';
|
|
736
|
+
tokens[i][0] = 'TYPE_ANNOTATION'; enterType(); continue;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const f = stack[stack.length - 1];
|
|
742
|
+
if (f && (f.kind === 'param' || f.kind === 'defparam')) {
|
|
743
|
+
if (tag === ',') { f.sawEq = false; f.sawType = false; continue; }
|
|
744
|
+
if (tag === '=') { f.sawEq = true; continue; }
|
|
745
|
+
if (tag === ':' && !f.sawEq && !f.sawType) {
|
|
746
|
+
const prev = tokens[i - 1];
|
|
747
|
+
const prevTag = prev?.[0];
|
|
748
|
+
if (prevTag === 'PROPERTY' || prevTag === 'IDENTIFIER') {
|
|
749
|
+
if (prevTag === 'PROPERTY' && tokens[i - 2]?.[0] !== '@') prev[0] = 'IDENTIFIER';
|
|
750
|
+
tokens[i][0] = 'TYPE_ANNOTATION';
|
|
751
|
+
f.sawType = true;
|
|
752
|
+
enterType();
|
|
753
|
+
} else if (prevTag === '}' || prevTag === ']' || prevTag === 'INDEX_END') {
|
|
754
|
+
// Context A — external destructuring param type, TS-style:
|
|
755
|
+
// `({pat}: Type)` / `([pat]: Type)`. The `:` follows the ROOT pattern
|
|
756
|
+
// close (the stack top is this param frame, so the pattern's own
|
|
757
|
+
// brackets have already been popped — a nested pattern's `:` is seen
|
|
758
|
+
// while an inner frame is on top and stays a rename). In-pattern `:`
|
|
759
|
+
// therefore keeps its destructuring-rename meaning; the type lives
|
|
760
|
+
// outside the pattern, after `}`/`]`.
|
|
761
|
+
tokens[i][0] = 'TYPE_ANNOTATION';
|
|
762
|
+
f.sawType = true;
|
|
763
|
+
enterType();
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
315
770
|
// Collect type expression tokens starting at position j, respecting brackets
|
|
316
|
-
function collectTypeExpression(tokens, j) {
|
|
771
|
+
function collectTypeExpression(tokens, j, opts = {}) {
|
|
317
772
|
let typeTokens = [];
|
|
318
773
|
let depth = 0;
|
|
319
774
|
let bracketStack = []; // tracks innermost open bracket: '{', '[', '(', '<'
|
|
@@ -365,6 +820,12 @@ function collectTypeExpression(tokens, j) {
|
|
|
365
820
|
|
|
366
821
|
// Delimiters that end the type at depth 0
|
|
367
822
|
if (depth === 0) {
|
|
823
|
+
// Arrow-return context: a depth-0 `=>` is the arrow OPERATOR, so it ends
|
|
824
|
+
// the return type (symmetric with `->` below). A function-type return
|
|
825
|
+
// must therefore be parenthesized as a whole — see the foot-gun guard in
|
|
826
|
+
// the TYPE_ANNOTATION handler. Everywhere else `=>` is a TS function-type
|
|
827
|
+
// arrow and is collected (e.g. `cb:: () => void`).
|
|
828
|
+
if (opts.stopAtFatArrow && tTag === '=>') break;
|
|
368
829
|
// After =>, INDENT wraps the return type body — collect through OUTDENT
|
|
369
830
|
if (tTag === 'INDENT' && typeTokens.length > 0 && typeTokens[typeTokens.length - 1][0] === '=>') {
|
|
370
831
|
j++; // skip INDENT
|