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.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rip-lang",
3
- "version": "3.16.3",
3
+ "version": "3.17.1",
4
4
  "description": "A modern language that compiles to JavaScript",
5
5
  "type": "module",
6
6
  "main": "src/compiler.js",
package/src/compiler.js CHANGED
@@ -1645,15 +1645,20 @@ export class CodeEmitter {
1645
1645
  // no longer travels through the `.d.ts` header and the overload-interleave
1646
1646
  // splice (typecheck.js). Off by default — runtime / `-c` output is unchanged.
1647
1647
  //
1648
+ // `node` is the type-bearing s-expression head: the function NAME for `def`,
1649
+ // or the `->`/`=>` arrow operator for arrow functions (the lexer adapter
1650
+ // Object.assigns each token's `.data` — including `returnType` — onto its
1651
+ // String-wrapper, so both carry the annotation).
1652
+ //
1648
1653
  // Only an EXPLICIT user annotation (`def f():: T`) is emitted, verbatim via
1649
1654
  // `ripToTs`. `void` is deliberately NOT synthesized for `!` functions: on an
1650
1655
  // async function `: void` (or any non-Promise type) is itself a TS1064, so a
1651
1656
  // synthesized annotation would fire a spurious error on legitimate async
1652
1657
  // side-effect functions once 1064 is recovered. Those keep flowing through
1653
1658
  // the header until a later slice handles them.
1654
- inlineReturnType(name) {
1659
+ inlineReturnType(node) {
1655
1660
  if (!this.options.inlineTypes) return '';
1656
- let rt = meta(name, 'returnType');
1661
+ let rt = meta(node, 'returnType');
1657
1662
  return rt ? `: ${ripToTs(rt)}` : '';
1658
1663
  }
1659
1664
 
@@ -1665,7 +1670,8 @@ export class CodeEmitter {
1665
1670
  let bodyCode = this.emitFunctionBody(body, params, sideEffectOnly);
1666
1671
  let isAsync = this.containsAwait(body);
1667
1672
  let isGen = this.containsYield(body);
1668
- let fn = `${isAsync ? 'async ' : ''}function${isGen ? '*' : ''}(${paramList}) ${bodyCode}`;
1673
+ let ret = this.inlineReturnType(sexpr?.[0]);
1674
+ let fn = `${isAsync ? 'async ' : ''}function${isGen ? '*' : ''}(${paramList})${ret} ${bodyCode}`;
1669
1675
  return context === 'value' ? `(${fn})` : fn;
1670
1676
  }
1671
1677
 
@@ -1674,7 +1680,13 @@ export class CodeEmitter {
1674
1680
  if ((!params || (Array.isArray(params) && params.length === 0)) && this.containsIt(body)) params = ['it'];
1675
1681
  let sideEffectOnly = sexpr.isVoid || false;
1676
1682
  let paramList = this.emitParamList(params);
1677
- let isSingle = params.length === 1 && typeof params[0] === 'string' &&
1683
+ // RFC 12 phase 2: an inline return type forces parenthesized params —
1684
+ // `x: R => …` is invalid TS, only `(x): R => …`. Latent until fat-arrow
1685
+ // return types are expressible (the `=>` annotation-vs-operator grammar
1686
+ // ambiguity is a separate slice); `ret` is `''` today, so output is
1687
+ // unchanged.
1688
+ let ret = this.inlineReturnType(sexpr?.[0]);
1689
+ let isSingle = !ret && params.length === 1 && typeof params[0] === 'string' &&
1678
1690
  !paramList.includes('=') && !paramList.includes('...') &&
1679
1691
  !paramList.includes('[') && !paramList.includes('{');
1680
1692
  let paramSyntax = isSingle ? paramList : `(${paramList})`;
@@ -1688,18 +1700,18 @@ export class CodeEmitter {
1688
1700
  if (exprHead !== 'return' && !STMT_ONLY.has(exprHead)) {
1689
1701
  let code = this.emit(expr, 'value');
1690
1702
  if (code[0] === '{') code = `(${code})`;
1691
- return `${prefix}${paramSyntax} => ${code}`;
1703
+ return `${prefix}${paramSyntax}${ret} => ${code}`;
1692
1704
  }
1693
1705
  }
1694
1706
  if (!Array.isArray(body) || body[0] !== 'block') {
1695
1707
  let code = this.emit(body, 'value');
1696
1708
  if (code[0] === '{') code = `(${code})`;
1697
- return `${prefix}${paramSyntax} => ${code}`;
1709
+ return `${prefix}${paramSyntax}${ret} => ${code}`;
1698
1710
  }
1699
1711
  }
1700
1712
 
1701
1713
  let bodyCode = this.emitFunctionBody(body, params, sideEffectOnly);
1702
- return `${prefix}${paramSyntax} => ${bodyCode}`;
1714
+ return `${prefix}${paramSyntax}${ret} => ${bodyCode}`;
1703
1715
  }
1704
1716
 
1705
1717
  emitReturn(head, rest, context, sexpr) {
package/src/dts.js CHANGED
@@ -157,9 +157,11 @@ export function emitTypes(tokens, sexpr = null, source = '', schemaBehavior = nu
157
157
  let dd = 0;
158
158
  while (j < tokens.length) {
159
159
  let dt = tokens[j];
160
- if (dt[0] === '(' || dt[0] === '[' || dt[0] === '{') dd++;
161
- if (dt[0] === ')' || dt[0] === ']' || dt[0] === '}') {
162
- if (dd === 0) break; // closing bracket of enclosing structure
160
+ if (dt[0] === '(' || dt[0] === '[' || dt[0] === '{' ||
161
+ dt[0] === 'CALL_START' || dt[0] === 'PARAM_START' || dt[0] === 'INDEX_START') dd++;
162
+ if (dt[0] === ')' || dt[0] === ']' || dt[0] === '}' ||
163
+ dt[0] === 'CALL_END' || dt[0] === 'PARAM_END' || dt[0] === 'INDEX_END') {
164
+ if (dd === 0) break; // closing bracket of the enclosing param list / pattern
163
165
  dd--;
164
166
  }
165
167
  if (dd === 0 && dt[1] === ',') break;
@@ -348,29 +350,34 @@ export function emitTypes(tokens, sexpr = null, source = '', schemaBehavior = nu
348
350
  continue;
349
351
  }
350
352
 
351
- // Destructured object parameter: { a, b }
353
+ // Destructured object parameter: { a, b }. An EXTERNAL type annotation
354
+ // `({a, b}: {a: T, b: U})` lands on the closing `}` token's data.type and
355
+ // takes precedence over a type reconstructed from in-pattern annotations.
352
356
  if (tok[0] === '{') {
353
357
  depth--; // undo the depth++ from nesting tracker above
354
358
  let result = collectDestructuredObj(tokens, j);
355
359
  j = result.endJ;
356
- if (result.hasAnyType) {
357
- params.push(`${result.patternStr}: ${result.typeStr}`);
358
- } else {
359
- params.push(result.patternStr);
360
- }
360
+ let ext = tokens[j - 1]?.data?.type;
361
+ let patternStr = result.patternStr;
362
+ // A whole-pattern default (`{…}: T = {}`) makes the param optional.
363
+ if (tokens[j]?.[0] === '=') { patternStr += '?'; j = skipDefault(tokens, j); }
364
+ if (ext) params.push(`${patternStr}: ${tsType(ext)}`);
365
+ else if (result.hasAnyType) params.push(`${patternStr}: ${result.typeStr}`);
366
+ else params.push(patternStr);
361
367
  continue;
362
368
  }
363
369
 
364
- // Destructured array parameter: [ a, b ]
370
+ // Destructured array parameter: [ a, b ] (external type lands on `]`).
365
371
  if (tok[0] === '[') {
366
372
  depth--; // undo the depth++ from nesting tracker above
367
373
  let result = collectDestructuredArr(tokens, j);
368
374
  j = result.endJ;
369
- if (result.hasAnyType) {
370
- params.push(`${result.patternStr}: ${result.typeStr}`);
371
- } else {
372
- params.push(result.patternStr);
373
- }
375
+ let ext = tokens[j - 1]?.data?.type;
376
+ let patternStr = result.patternStr;
377
+ if (tokens[j]?.[0] === '=') { patternStr += '?'; j = skipDefault(tokens, j); }
378
+ if (ext) params.push(`${patternStr}: ${tsType(ext)}`);
379
+ else if (result.hasAnyType) params.push(`${patternStr}: ${result.typeStr}`);
380
+ else params.push(patternStr);
374
381
  continue;
375
382
  }
376
383
 
package/src/lexer.js CHANGED
@@ -893,6 +893,13 @@ export class Lexer {
893
893
  else if (tk[0] === 'SHIFT' && tk[1] === '>>>') depth += 3;
894
894
  else if (tk[0] === 'COMPARE' && tk[1] === '<') depth--;
895
895
  if (depth === 0 && tk[0] === 'TYPE_ANNOTATION') return false;
896
+ // A single `:` return type (Rip 3.17) — `…): Generic<…>` — closes a
897
+ // generic just like `::` does. Recognize it when the `:` directly
898
+ // follows a param-list close (mirrors the `tagParameters` acceptance).
899
+ if (depth === 0 && tk[0] === ':') {
900
+ let pv = this.tokens[k - 1];
901
+ if (pv && (pv[0] === ')' || pv[0] === 'PARAM_END' || pv[0] === 'CALL_END')) return false;
902
+ }
896
903
  if (depth === 0 && tk[0] === 'IDENTIFIER' && tk[1] === 'type') return false;
897
904
  if (tk[0] === 'INTERFACE') return false;
898
905
  if (tk[0] === 'TERMINATOR' || tk[0] === 'INDENT' || tk[0] === 'OUTDENT') {
@@ -1534,7 +1541,10 @@ export class Lexer {
1534
1541
  (tg === 'COMPARE' && tk[1] === '<')) {
1535
1542
  depth--;
1536
1543
  } else if (depth === 0) {
1537
- if (tg === 'TYPE_ANNOTATION') {
1544
+ // A return-type annotation sits between the param `)` and the arrow.
1545
+ // Accept both `::` (TYPE_ANNOTATION) and a single `:` (Rip 3.17) as
1546
+ // the marker, but only when it directly follows the param-list `)`.
1547
+ if (tg === 'TYPE_ANNOTATION' || tg === ':') {
1538
1548
  if (j > 0 && this.tokens[j - 1][0] === ')') found = j - 1;
1539
1549
  break;
1540
1550
  }
@@ -1545,6 +1555,25 @@ export class Lexer {
1545
1555
  }
1546
1556
  if (found < 0) return this.tagDoIife();
1547
1557
  closeIdx = found;
1558
+ } else {
1559
+ // The token before the arrow is `)`. Normally that's the param-list
1560
+ // close — but it can also close a PARENTHESIZED return type:
1561
+ // `(x):: (R) =>`. Disambiguate by finding this `)`'s matching `(`; if a
1562
+ // `TYPE_ANNOTATION` (`::`) precedes that `(` and a `)` precedes the `::`,
1563
+ // this group is the return type and the real param close is that earlier
1564
+ // `)`. (A bare `(x) =>` has no `::` before its `(`, so it's unchanged.)
1565
+ let d = 0, op = -1;
1566
+ for (let k = closeIdx; k >= 0; k--) {
1567
+ let tg = this.tokens[k][0];
1568
+ if (tg === ')' || tg === 'CALL_END' || tg === 'PARAM_END') d++;
1569
+ else if (tg === '(' || tg === 'CALL_START' || tg === 'PARAM_START') {
1570
+ if (--d === 0) { op = k; break; }
1571
+ }
1572
+ }
1573
+ if (op > 1 && this.tokens[op - 1][0] === 'TYPE_ANNOTATION' &&
1574
+ this.tokens[op - 2]?.[0] === ')') {
1575
+ closeIdx = op - 2;
1576
+ }
1548
1577
  }
1549
1578
 
1550
1579
  let i = closeIdx;
package/src/typecheck.js CHANGED
@@ -11,6 +11,7 @@
11
11
  // type errors mapped back to Rip source positions.
12
12
 
13
13
  import { Compiler, getStdlibCode } from './compiler.js';
14
+ import { Lexer } from './lexer.js';
14
15
  import { STDLIB_TYPE_DECLS } from './stdlib.js';
15
16
  import { INTRINSIC_TYPE_DECLS, INTRINSIC_FN_DECL, ARIA_TYPE_DECLS, SIGNAL_INTERFACE, SIGNAL_FN, COMPUTED_INTERFACE, COMPUTED_FN, EFFECT_FN, ripDestructuredNames } from './dts.js';
16
17
  import './schema/loader-server.js'; // registers full schema runtime provider
@@ -367,18 +368,31 @@ export function walkRoutesDir(routesDir) {
367
368
  // Detect type annotations (:: followed by space or =) ignoring comments,
368
369
  // string literals, and prototype syntax (Class::method).
369
370
  export function hasTypeAnnotations(source) {
371
+ // Type aliases / interfaces don't lower to TYPE_ANNOTATION tokens, so detect
372
+ // them by name from the source first (cheap, and robust mid-edit).
370
373
  let inHeredoc = false;
371
- return source.split('\n').some(line => {
372
- // Track heredoc boundaries (''' or """)
374
+ const hasTypeDecl = source.split('\n').some(line => {
373
375
  const ticks = (line.match(/'''|"""/g) || []);
374
376
  for (const t of ticks) inHeredoc = !inHeredoc;
375
377
  if (inHeredoc) return false;
376
- // Strip comment
377
- line = line.replace(/#.*$/, '');
378
- // Strip string literals (single and double quoted)
379
- line = line.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
380
- return /::[ \t=]/.test(line) || /(?:^|export\s+)type\s+[A-Z]/.test(line.trimStart());
378
+ line = line.replace(/#.*$/, '').trimStart();
379
+ return /^(?:export\s+)?(?:type\s+[A-Z]|interface\s)/.test(line);
381
380
  });
381
+ if (hasTypeDecl) return true;
382
+ // Type ANNOTATIONS — single-colon (Rip 3.17) or `::` — both lower to a
383
+ // TYPE_ANNOTATION token after the rewriter runs. Tokenizing is the accurate
384
+ // test (a string scan can't tell `x: T = v` from the object `{x: T}`); fall
385
+ // back to the literal `::` heuristic if tokenization fails on a partial buffer.
386
+ try {
387
+ // The rewriter consumes type annotations into `.data.type` /
388
+ // `.data.returnType` on the annotated token (they don't survive as
389
+ // TYPE_ANNOTATION tokens), so detect that metadata.
390
+ for (const t of new Lexer().tokenize(source, { rewrite: true }))
391
+ if (t.data && (t.data.type || t.data.returnType)) return true;
392
+ return false;
393
+ } catch {
394
+ return /::[ \t=]/.test(source.replace(/#.*$/gm, ''));
395
+ }
382
396
  }
383
397
 
384
398
  export function countLines(str) {
@@ -1153,6 +1167,82 @@ function replaceFnParams(line, newParams) {
1153
1167
  return depth === 0 ? line.slice(0, idx + 1) + newParams + line.slice(i - 1) : line;
1154
1168
  }
1155
1169
 
1170
+ // Skip a string/template/regex literal starting at `s[i]` (the opening quote),
1171
+ // returning the index just past the closing quote. Honors backslash escapes.
1172
+ function skipStringLiteral(s, i) {
1173
+ const q = s[i];
1174
+ i++;
1175
+ while (i < s.length) {
1176
+ if (s[i] === '\\') { i += 2; continue; }
1177
+ if (s[i] === q) return i + 1;
1178
+ i++;
1179
+ }
1180
+ return i;
1181
+ }
1182
+
1183
+ // Recognize a compiled module-scope fat-arrow assignment for `name`:
1184
+ // NAME = (params) => … NAME = ident => …
1185
+ // NAME = async (params) => … NAME = async ident => …
1186
+ // Returns null when `line` is not such an assignment, else
1187
+ // `{ hasInlineReturn }` — whether an inline `: T` return annotation already
1188
+ // sits between the params and `=>`. (There is no generator-arrow form; thin
1189
+ // arrows lower to `= function(` and are matched separately.) A structural
1190
+ // scan, not a regex: generated param lists can contain nested parens, default
1191
+ // values, and string literals, so the params span and the implementation `=>`
1192
+ // must be located by balanced, string-aware scanning.
1193
+ function analyzeArrowImpl(line, name) {
1194
+ const pre = new RegExp(`^\\s*${name}\\s*=\\s*`).exec(line);
1195
+ if (!pre) return null;
1196
+ const s = line;
1197
+ let i = pre[0].length;
1198
+ const skipWs = () => { while (i < s.length && (s[i] === ' ' || s[i] === '\t')) i++; };
1199
+ skipWs();
1200
+ // `async ` is the modifier — but only when real params follow. A bare
1201
+ // identifier param named `async` (`NAME = async => …`) is not the modifier;
1202
+ // don't consume it as one (it would otherwise mis-parse as no params).
1203
+ if (s.startsWith('async', i) && /\s/.test(s[i + 5] || '')) {
1204
+ let k = i + 5;
1205
+ while (k < s.length && (s[k] === ' ' || s[k] === '\t')) k++;
1206
+ if (!(s[k] === '=' && s[k + 1] === '>')) i = k;
1207
+ }
1208
+
1209
+ // Parameter list: `(...)` (balanced, string-aware) or a bare identifier.
1210
+ if (s[i] === '(') {
1211
+ let depth = 0;
1212
+ for (; i < s.length; i++) {
1213
+ const c = s[i];
1214
+ if (c === '"' || c === "'" || c === '`') { i = skipStringLiteral(s, i) - 1; continue; }
1215
+ if (c === '(') depth++;
1216
+ else if (c === ')' && --depth === 0) { i++; break; }
1217
+ }
1218
+ if (depth !== 0) return null;
1219
+ } else if (/[A-Za-z_$]/.test(s[i] || '')) {
1220
+ while (i < s.length && /[\w$]/.test(s[i])) i++;
1221
+ } else {
1222
+ return null;
1223
+ }
1224
+ skipWs();
1225
+
1226
+ // Optional inline return type `: T` between the params and `=>`. Latent
1227
+ // today (fat-arrow return types aren't expressible yet), but recognized so
1228
+ // the self-sufficiency check stays correct once they are.
1229
+ let hasInlineReturn = false;
1230
+ if (s[i] === ':') {
1231
+ hasInlineReturn = true;
1232
+ let depth = 0;
1233
+ for (; i < s.length; i++) {
1234
+ const c = s[i];
1235
+ if (c === '"' || c === "'" || c === '`') { i = skipStringLiteral(s, i) - 1; continue; }
1236
+ if (c === '=' && s[i + 1] === '>' && depth === 0) break;
1237
+ if (c === '(' || c === '[' || c === '{' || c === '<') depth++;
1238
+ else if (c === ')' || c === ']' || c === '}' || c === '>') depth--;
1239
+ }
1240
+ skipWs();
1241
+ }
1242
+
1243
+ return (s[i] === '=' && s[i + 1] === '>') ? { hasInlineReturn } : null;
1244
+ }
1245
+
1156
1246
  // Depth-aware split of a parameter list on top-level commas. Respects
1157
1247
  // nested parens, brackets, braces, and angle brackets so callback types
1158
1248
  // like `(fn: (x: number) => void)` and generic types like `Map<K, V>`
@@ -1422,22 +1512,26 @@ export function compileForCheck(filePath, source, compiler, opts = {}) {
1422
1512
  const injections = [];
1423
1513
  const moved = new Set();
1424
1514
  for (const fn of fnSigs) {
1425
- // Match the impl in either of two shapes:
1515
+ // Match the impl in one of three shapes:
1426
1516
  // 1. top-level: `function NAME(`, `function NAME<`,
1427
1517
  // `async function NAME(`, `export function NAME(`
1428
- // 2. bare-name arrow assignment: `NAME = function(`,
1518
+ // 2. thin-arrow assignment: `NAME = function(`,
1429
1519
  // `NAME = async function(`, `NAME = function*(`
1430
- // Pattern (2) is how Rip emits arrow assignments at module
1431
- // scope: `name = (...) -> ...` becomes `name = function(...) {…}`.
1432
- // We deliberately do NOT match `obj.NAME = function(`: the DTS
1433
- // emits `declare function NAME(...)` only for module-scope,
1434
- // bare-name arrow assignments. A property-style match would
1435
- // pick up an unrelated `obj.NAME = ...` line that happens to
1436
- // share the function name and apply the wrong signature there.
1520
+ // (`name = (...) -> ...` lowers to `name = function(...) {…}`)
1521
+ // 3. fat-arrow assignment: `NAME = (...) => …`, `NAME = x => …`,
1522
+ // `NAME = async (...) => …` (`name = (...) => ...`). Without this
1523
+ // the own-file `declare function NAME` survives next to the
1524
+ // `let NAME` / `NAME = =>` binding and TS reports TS2630
1525
+ // ("cannot assign to NAME because it is a function") visible in
1526
+ // the editor shadow even when CLI `rip check` masks it.
1527
+ // We deliberately do NOT match `obj.NAME = …`: the DTS emits
1528
+ // `declare function NAME(...)` only for module-scope, bare-name
1529
+ // assignments. A property-style match would pick up an unrelated
1530
+ // `obj.NAME = ...` line sharing the name and apply the wrong signature.
1437
1531
  const topLevelPat = new RegExp(`^(?:export\\s+)?(?:async\\s+)?function\\s+${fn.name}\\s*[(<]`);
1438
1532
  const arrowAssignPat = new RegExp(`^\\s*${fn.name}\\s*=\\s*(?:async\\s+)?function\\s*\\*?\\s*\\(`);
1439
1533
  for (let j = 0; j < cl.length; j++) {
1440
- if (topLevelPat.test(cl[j]) || arrowAssignPat.test(cl[j])) {
1534
+ if (topLevelPat.test(cl[j]) || arrowAssignPat.test(cl[j]) || analyzeArrowImpl(cl[j], fn.name)) {
1441
1535
  injections.push({ codeLine: j, sig: fn.sig });
1442
1536
  moved.add(fn.idx);
1443
1537
  break;
@@ -1536,16 +1630,60 @@ export function compileForCheck(filePath, source, compiler, opts = {}) {
1536
1630
  const nm = inj.sig.match(/function\s+(\w+)/)?.[1];
1537
1631
  if (nm) sigCountByName.set(nm, (sigCountByName.get(nm) || 0) + 1);
1538
1632
  }
1633
+ // A `{…}`/`[…]` destructuring parameter can't be reliably typed inline:
1634
+ // an EXTERNAL type (`({a}: {a: T})`) is attached to the closing-bracket
1635
+ // token and lost to the AST-based inline emitter, while an in-pattern
1636
+ // type reconstructs fine. Rather than split those cases, force the param
1637
+ // copy from the `.d.ts` signature (which carries the correct type for
1638
+ // both forms via dts.js) whenever the param list opens a pattern.
1639
+ function paramListHasPattern(line) {
1640
+ const open = line.indexOf('(');
1641
+ if (open < 0) return false;
1642
+ let depth = 0, atParamStart = false;
1643
+ for (let k = open; k < line.length; k++) {
1644
+ const c = line[k];
1645
+ if (c === '(') { depth++; if (depth === 1) atParamStart = true; continue; }
1646
+ if (c === ')') { if (--depth === 0) return false; continue; }
1647
+ if (depth !== 1) continue;
1648
+ if (c === ',') { atParamStart = true; continue; }
1649
+ if (c === ' ' || c === '\t') continue;
1650
+ if (atParamStart && (c === '{' || c === '[')) return true;
1651
+ atParamStart = false;
1652
+ }
1653
+ return false;
1654
+ }
1539
1655
  function isSelfSufficient(inj) {
1540
1656
  const nm = inj.sig.match(/function\s+(\w+)/)?.[1];
1541
1657
  if (!nm || sigCountByName.get(nm) !== 1) return false;
1542
1658
  if (extractTypeParams(inj.sig)) return false;
1543
1659
  const impl = cl[inj.codeLine];
1660
+ if (paramListHasPattern(impl)) return false;
1661
+ // Arrow detection is authoritative and runs first: it's a precise
1662
+ // structural scan, so a fat-arrow line can't be misread by the
1663
+ // function-form brace heuristic below (which would otherwise pick a
1664
+ // nested object-literal `{` on an expression body). A fat-arrow
1665
+ // assignment (`NAME = (params) => …`) emits its params inline, so it
1666
+ // is self-sufficient when it carries an inline return type, OR when
1667
+ // the header has no return type to preserve. Fat-arrow return types
1668
+ // aren't expressible yet, so the header never carries one — moving it
1669
+ // (no copy) is complete and lossless. Once they are, an arrow that
1670
+ // lacks the inline return while the header has one is NOT self-
1671
+ // sufficient and falls through to the copy fallback (return preserved).
1672
+ const arrow = analyzeArrowImpl(impl, nm);
1673
+ if (arrow) {
1674
+ if (arrow.hasInlineReturn) return true;
1675
+ if (!hasExplicitReturn(inj.sig)) return true;
1676
+ return false;
1677
+ }
1678
+ // Function-form impl (`def`, thin-arrow `= function(`): self-sufficient
1679
+ // when an inline return type sits between the params `)` and the `{`.
1544
1680
  const braceIdx = impl.lastIndexOf('{');
1545
- if (braceIdx < 0) return false;
1546
- const head = impl.slice(0, braceIdx);
1547
- const afterParen = head.slice(head.lastIndexOf(')') + 1).trim();
1548
- return afterParen.startsWith(':');
1681
+ if (braceIdx >= 0) {
1682
+ const head = impl.slice(0, braceIdx);
1683
+ const afterParen = head.slice(head.lastIndexOf(')') + 1).trim();
1684
+ if (afterParen.startsWith(':')) return true;
1685
+ }
1686
+ return false;
1549
1687
  }
1550
1688
 
1551
1689
  // First pass: copy typed params AND return types from signatures to
@@ -1609,7 +1747,11 @@ export function compileForCheck(filePath, source, compiler, opts = {}) {
1609
1747
  // would force it to `any`. Functions with any untyped param would fire
1610
1748
  // TS7006 twice (once on the injected sig, once on the impl) at the same
1611
1749
  // source position — skip the injection so the user sees a single error.
1612
- const overloads = injections.filter(inj => !isSelfSufficient(inj) && hasExplicitReturn(inj.sig) && !hasUntypedParam(inj.sig));
1750
+ // Destructuring-param functions get their full typed signature copied
1751
+ // onto the implementation (above); an extra overload signature is
1752
+ // redundant and, for a rename pattern (`{name: userName}`), its
1753
+ // body-less form makes `userName` an "unused renaming" (TS2842). Skip it.
1754
+ const overloads = injections.filter(inj => !isSelfSufficient(inj) && hasExplicitReturn(inj.sig) && !hasUntypedParam(inj.sig) && !paramListHasPattern(cl[inj.codeLine]));
1613
1755
 
1614
1756
  // Adjust reverseMap: each overload injection shifts subsequent code lines down by 1.
1615
1757
  // Compare against the original genLine (not genLine + offset) because bottom-up