Skip to content

Commit 43bdee5

Browse files
Improve error handling for non-constant index access and unsupported spread in object literals (#297)
1 parent 20ba06c commit 43bdee5

2 files changed

Lines changed: 274 additions & 5 deletions

File tree

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
# `llvm_unreachable("not implemented")` audit
2+
3+
Status: **2 confirmed crashes fixed (this pass), ~120 markers still uninvestigated**
4+
written as a roadmap for continuing this audit, not a claim that the sweep is
5+
complete. Triggered by a user request to review every "not implemented" marker
6+
in the codebase and see which ones can be implemented.
7+
8+
## 1. Scope and method
9+
10+
A repo-wide search for `not implemented`/`llvm_unreachable` across
11+
`lib/TypeScript/*.cpp` and `include/TypeScript/{MLIRLogic,LowerToLLVM}/*.h`
12+
turns up **~130 raw matches** (some are two-line sites: an `LLVM_DEBUG` print
13+
immediately followed by the `llvm_unreachable`, counted here as one site).
14+
Grep command used:
15+
16+
```
17+
grep -rn "not implemented\|Not implemented\|NOT IMPLEMENTED\|not yet implemented\|NotImplemented" \
18+
--include=*.cpp --include=*.h lib include tslang
19+
```
20+
21+
These markers span three very different situations that look identical in a
22+
grep, and need to be told apart before deciding what "implement" even means
23+
for each:
24+
25+
1. **Legitimate diagnostics.** A handful are `emitError(...)` calls for
26+
genuinely invalid source (e.g. `MLIRGenClasses.cpp:1919`, "Abstract method
27+
'X' is not implemented in 'Y'" — a normal missing-override error;
28+
`MLIRGenVariables.cpp:285/324`, array binding pattern spread/type
29+
mismatches). These already produce clean compiler errors. Not gaps.
30+
2. **Generic exhaustiveness fallbacks.** The majority (~90+ of the raw
31+
matches) are `.Default([&](auto type) { llvm_unreachable("not
32+
implemented"); })` at the bottom of an MLIR `TypeSwitch` chain, mostly in
33+
low-level LLVM-lowering, RTTI, and cast-helper code
34+
(`LowerToLLVM.cpp`, `CastLogicHelper.h`, `LLVMRTTIHelperVC*.h`,
35+
`MLIRRTTIHelperVC*.h`, `MLIRTypeHelper.h`'s `funcRef` family, etc.). These
36+
fire only if a `mlir::Type` value reaches that specific conversion/lowering
37+
stage with a kind the author never handled there. Some are real gaps; many
38+
are defensive guards for states the type system already rules out earlier
39+
in the pipeline (see §3 for a proven example) — **reachability is unknown
40+
without testing each one**, and that is genuinely the expensive part.
41+
3. **Named, specific gaps.** ~40 markers carry a message naming the exact
42+
scenario ("SpreadAssignment not implemented for type: X", "TypeOf NOT
43+
IMPLEMENTED for Type: X", "not implemented (index)", "not implemented
44+
(ElementAccessExpression)", …). These are much cheaper to turn into a
45+
reproduction: the message plus its surrounding `if`/`TypeSwitch` branches
46+
usually tells you exactly what source-level construct is missing.
47+
48+
Of these, only **6 sites were actually tested** this pass (2 named markers
49+
confirmed reachable and fixed, 1 generic fallback confirmed dead, 3 more
50+
named markers read but not yet reproduced). Everything else in §5 is an
51+
inventory, not a verdict.
52+
53+
## 2. Reproduction recipe used
54+
55+
For each candidate: read the surrounding code to infer what TS source
56+
pattern would make execution reach that branch, write a minimal `.ts` file
57+
exercising it, and run it through the actual compiler:
58+
59+
```
60+
test-runner.exe <path-to-repro.ts>
61+
```
62+
63+
(no `-jit`/`-shared` needed for a single-file reachability check). A crash
64+
looks like:
65+
66+
```
67+
not implemented
68+
UNREACHABLE executed at I:\...\MLIRGenImpl.h:5797!
69+
```
70+
71+
If it crashes, the marker is real and reachable. If it compiles/runs, either
72+
the type is resolved away earlier in the pipeline (like §3), or the guess
73+
about the trigger was wrong and needs another attempt.
74+
75+
## 3. Confirmed dead code (proof-of-concept for triaging fallbacks)
76+
77+
`LowerToLLVM.cpp:6267`:
78+
79+
```cpp
80+
converter.addConversion([&](mlir_ts::IntersectionType type) {
81+
llvm_unreachable("type usage (IntersectionType) is not implemented");
82+
return mlir::Type();
83+
});
84+
```
85+
86+
Intersection types (`A & B`) are exercised by two existing, currently-passing
87+
tests (`00intersection_type.ts`, `00intersection_type_generic.ts`,
88+
`test-compile-00-intersection-type[-generic]` / `test-jit-...` in
89+
`test/tester/CMakeLists.txt`), so an `IntersectionType` MLIR value clearly
90+
*can* exist. It just never survives to LLVM type conversion — it must be
91+
resolved into its concrete merged/flattened type earlier in MLIRGen. This
92+
one marker is confirmed unreachable for any currently-expressible source
93+
program. **This is the template for triaging the remaining ~90 generic
94+
fallbacks**: find an existing test that plausibly produces the type in
95+
question, confirm it passes, and if so the fallback is very likely dead for
96+
today's feature set (not proof for all future features, but proof for now).
97+
98+
## 4. Fixed this pass
99+
100+
Both fixes are in `lib/TypeScript/MLIRGenImpl.h`, both converted a hard
101+
`llvm_unreachable` crash into an `emitError` + graceful failure, matching the
102+
pattern already established at `MLIRGenVariables.cpp:285` (Array Binding
103+
Pattern spread) — nothing new was invented, this is the existing
104+
"fail loud with a message, don't crash" convention applied to two spots that
105+
hadn't gotten it yet.
106+
107+
### 4.1 `obj[dynamicKey]` — non-constant index on a tuple/object-literal value
108+
109+
`mlirGenElementAccessTuple` (`MLIRGenImpl.h`, was line 5797):
110+
111+
```ts
112+
function main() {
113+
const obj = { a: 1, b: 2 };
114+
let key = "a";
115+
print(obj[key]); // key is a runtime variable, not a literal
116+
}
117+
```
118+
119+
crashed with `UNREACHABLE executed at MLIRGenImpl.h:5797`. Root cause:
120+
tuples/object-literals in this compiler lower to a **fixed-layout struct**
121+
(each field resolved to a specific byte offset at compile time), not a
122+
dynamic hash map. When the index expression is a compile-time constant, the
123+
existing code resolves it to a field the normal way; the crash was the `else`
124+
branch, hit whenever the index is a genuine runtime value. This is not
125+
missing code so much as **a real limitation of the current object
126+
representation** — properly "implementing" `obj[runtimeKey]` in general would
127+
need a dynamic property-bag runtime representation (a different data
128+
structure entirely, not a small patch). Converted the crash to:
129+
130+
```cpp
131+
emitError(location) << "Element access with a non-constant index is not supported on this type; "
132+
"only array types and constant keys (obj[\"literal\"]) can be indexed";
133+
return ValueOrLogicalResult(mlir::failure());
134+
```
135+
136+
### 4.2 Spreading a non-struct-like value into an object literal
137+
138+
The `SpreadAssignment` `TypeSwitch` inside object-literal codegen
139+
(`MLIRGenImpl.h`, was line 7863) only handles spreading a
140+
`TupleType`/`ConstTupleType`/`InterfaceType`/`ClassType`/`ObjectType` into
141+
`{...expr}`. Anything else hit the `Default` branch:
142+
143+
```ts
144+
function main() {
145+
const arr = [1, 2, 3];
146+
const obj = { ...arr }; // crash
147+
}
148+
function main2() {
149+
let x: {a: number} | number[] = { a: 1 };
150+
const obj = { ...x }; // crash, same site
151+
}
152+
```
153+
154+
Unlike §4.1, this genuinely **is** a missing feature (array spread would need
155+
to synthesize numeric-string-keyed fields `"0"`, `"1"`, …; union spread would
156+
need a runtime type-tag dispatch) rather than an architectural wall — it just
157+
wasn't scoped/implemented for this pass. Converted to:
158+
159+
```cpp
160+
emitError(location) << "Spread in an object literal is not supported for type: " << to_print(type);
161+
return mlir::failure();
162+
```
163+
164+
Both verified individually (clean diagnostic, no crash) and via the full
165+
suite (`ctest -C Debug -j8`: 829/829, no regressions — these `Default`
166+
branches were never reached by any existing passing test).
167+
168+
## 5. Inventory of remaining markers (untested this pass)
169+
170+
Grouped by file. "Shape" is a guess from reading the surrounding code, not a
171+
verified verdict — see §2 for how to actually check one.
172+
173+
### 5.1 Named/specific (cheapest to investigate next — read the message + local branch, write a 5-line repro)
174+
175+
| Site | Message | Shape (unverified guess) |
176+
|---|---|---|
177+
| `MLIRGenAccessCall.cpp:1159` | not implemented (ElementAccessExpression) | `boxedObj[computedNonStringExpr]` — cousin of the fixed §4.1 site, one level up in the dispatch (only reached before deciding it's a tuple) |
178+
| `MLIRGenAccessCall.cpp:1219` | not implemented (ElementAccessExpression) | `enumValue[computedExpr]` with a non-constant index |
179+
| `MLIRGenAccessCall.cpp:1524` | not implemented | unread this pass |
180+
| `MLIRGenCast.cpp:1321-1322` | TypeOf NOT IMPLEMENTED for Type | inside a generated `__unbox<T>` helper (generic type-parameter unboxing from `any`); `.Default` for a type kind not in its explicit list (Tuple/Array/Enum/Union/Optional are plausible candidates) |
181+
| `MLIRGenCast.cpp:1498-1499` | TypeOf NOT IMPLEMENTED for Type | second, near-identical site — check if it's reachable via a different call path than 1321 |
182+
| `MLIRGenImpl.h:5330` | not implemented | unread |
183+
| `MLIRGenImpl.h:6732` | not implemented | unread |
184+
| `MLIRGenImpl.h:7314` | not implemented | unread |
185+
| `MLIRGenImpl.h:7418` | not implemented | unread |
186+
| `MLIRGenImpl.h:8164` | not implemented | unread |
187+
| `MLIRGenImpl.h:8382` / `:8400` / `:8426` | not implemented | unread, three sites close together — likely related |
188+
| `MLIRGenImpl.h:9342` | not implemented | unread |
189+
| `MLIRGenInterfaces.cpp:475` | not implemented yet | unread |
190+
| `MLIRGenInterfaces.cpp:932` / `:954` | not implemented | unread |
191+
| `MLIRGenTypes.cpp:183` | not implemented type declaration | unread |
192+
| `MLIRGenTypes.cpp:1474` / `:1567` / `:1876` / `:1910` / `:2001` / `:2204` / `:2210` / `:2661` / `:3401` | not implemented | unread, largest single-file cluster after MLIRGenImpl.h |
193+
| `LLVMCodeHelper.h:452` | array literal is not implemented(1) | likely the LLVM-lowering-side twin of the (confirmed-dead) `MLIRGenImpl.h:7875` "object literal is not implemented(1)" `else` branch — check the same way (is there any other `SyntaxKind` an array-literal element list can produce?) |
194+
| `UnaryBinLogicalOrHelper.h:42-43` | "Not implemented operator for type 1: 'X'" (`emitError`) then `llvm_unreachable` | **worth a quick look on its own**: this one already calls `emitError` (like §4's fix) but *still* falls through to `llvm_unreachable` right after — likely a copy-paste of the crash-then-message pattern that never got the `return` that would make the error actually graceful. If so, this is a one-line fix (drop the `llvm_unreachable`, return failure), no new investigation needed beyond confirming what emits it currently crashes instead of erroring cleanly. |
195+
196+
### 5.2 Generic `TypeSwitch::Default` exhaustiveness fallbacks (likely mostly dead, per §3's precedent — verify by checking whether an existing passing test already produces the relevant `mlir::Type` at that pipeline stage)
197+
198+
`MLIRGenClasses.cpp:603,635,1802,2269,2306` · `MLIRGenExpressions.cpp:530,552,988` ·
199+
`MLIRGenGenerics.cpp:424,542,911,1342` · `MLIRGenImpl.h:3203,3477,3803,4402,4518,6379,7080,7094` ·
200+
`MLIRGenInterfaces.cpp:656` · `CastLogicHelper.h:338,344,353,359,460,487,1002` (four of these say
201+
"must be processed at MLIR pass" — suggests a *specific*, documented reason
202+
they should be unreachable at the LLVM-lowering stage, worth reading before
203+
assuming they're arbitrary) · `CodeLogicHelper.h:241` · `OptionalLogicHelper.h:143,213` ·
204+
`UndefLogicHelper.h:74,107` · `MLIRCodeLogic.h:1218,1660,1680,1722` ·
205+
`MLIRPrinter.h:302-303,534` (type-name printing — a `Default` here would show
206+
up immediately as a printer test failure, so likely easy to check against
207+
`unittests/MLIRGen/TypeToString.cpp`'s existing coverage) · `MLIRTypeIterator.h:403-404` ·
208+
`Win32ExceptionPass.cpp:584`.
209+
210+
### 5.3 RTTI type-switch fallbacks (Windows/Linux variants — the Linux ones can't be exercised from this Windows dev box without a Linux/WSL build)
211+
212+
`LLVMRTTIHelperVCWin32.h:141,156,169` · `LLVMRTTIHelperVCLinux.h:113,128,142` ·
213+
`MLIRRTTIHelperVC.h:108` · `MLIRRTTIHelperVCWin32.h:216,226,241` ·
214+
`MLIRRTTIHelperVCLinux.h:146,161,182,202,217,230,399`.
215+
216+
### 5.4 `MLIRTypeHelper.h`'s `funcRef` family — interesting because directly unit-testable
217+
218+
`getReturnTypeFromFuncRef` (:732-733), `getParamFromFuncRef` (:755-756),
219+
`getFirstParamFromFuncRef` (:779-780), `getParamsFromFuncRef` (:805-806),
220+
`getParamsTupleTypeFromFuncRef` (:840, `llvm_unreachable` already commented
221+
out at :843 — someone deliberately silenced this one, worth understanding
222+
why before re-enabling), `getVarArgFromFuncRef` (:863-864), plus :410, :420,
223+
:899, :2108, :2256-2257, :2290, :2307, :2685, :2709. Unlike most of §5.2,
224+
these are **pure functions taking an `mlir::Type` and returning a piece of
225+
it** — exactly the shape `unittests/MLIRGen/TypeHelper.cpp` (added this
226+
session, see `declaration-printer-unit-tests`-style memory entries) already
227+
tests other `MLIRTypeHelper.h` functions with. Reachability here is testable
228+
*without* writing a `.ts` repro at all: construct the "wrong" `mlir::Type`
229+
input directly in a unit test and see what real callers expect the sensible
230+
behavior to be, the same way the existing `canWideTypeWithoutDataLoss` tests
231+
work.
232+
233+
## 6. Suggested next steps, in cost order
234+
235+
1. `UnaryBinLogicalOrHelper.h:42-43` — likely a one-line fix, already has the
236+
error message, just needs the crash removed.
237+
2. The rest of §5.1 (named/specific) — cheap repro-and-check, same recipe as
238+
§4.
239+
3. §5.4 (`funcRef` family) — extend `unittests/MLIRGen/TypeHelper.cpp` with
240+
direct calls instead of writing `.ts` repros; fast to iterate.
241+
4. §5.2 (generic fallbacks) — triage a handful against existing passing
242+
tests using §3's method before assuming any individual one is live.
243+
5. §5.3 (RTTI) — lowest priority from this (Windows) machine; the Linux
244+
variants need a WSL/Linux build to exercise at all, and even the Windows
245+
ones are deep in a code path (RTTI/exception typeinfo generation) that's
246+
hard to reach without a specific class-hierarchy-plus-exception scenario.
247+
248+
## 7. Non-goals / out of scope
249+
250+
- This document does not claim every remaining marker is a "real bug" —
251+
§3 demonstrates at least some meaningful fraction are dead code, and the
252+
true ratio across all ~120 is unknown.
253+
- Not attempting §4.2's actual missing feature (array/union spread into an
254+
object literal) in this pass — only converting its crash into a clean
255+
error. Implementing real array-spread semantics (numeric-string-keyed
256+
field synthesis) is a separate, scoped follow-up if ever prioritized.
257+
- Not attempting a dynamic-property-bag runtime representation for §4.1 —
258+
that is a different object model entirely, out of scope for a
259+
crash-to-error pass.

tslang/lib/TypeScript/MLIRGenImpl.h

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5792,9 +5792,14 @@ class MLIRGenImpl
57925792
}
57935793
else
57945794
{
5795-
LLVM_DEBUG(llvm::dbgs() << "\n!! index value: " << argumentExpression
5796-
<< ", check if tuple must be an array\n";);
5797-
llvm_unreachable("not implemented (index)");
5795+
// tuples/object-literals here compile to a fixed-layout struct (their fields
5796+
// are resolved to specific offsets at compile time), not a dynamic hash map -
5797+
// a runtime-computed (non-constant) index genuinely can't resolve to a
5798+
// specific field, so this is a real language limitation, not a missing code
5799+
// path. Fail with a clear diagnostic instead of crashing.
5800+
emitError(location) << "Element access with a non-constant index is not supported on this type; "
5801+
"only array types and constant keys (obj[\"literal\"]) can be indexed";
5802+
return ValueOrLogicalResult(mlir::failure());
57985803
}
57995804
}
58005805

@@ -7859,8 +7864,13 @@ class MLIRGenImpl
78597864
return mlir::success();
78607865
})
78617866
.Default([&](auto type) {
7862-
LLVM_DEBUG(llvm::dbgs() << "\n!! SpreadAssignment not implemented for type: " << type << "\n";);
7863-
llvm_unreachable("not implemented");
7867+
// spreading a tuple/const-tuple/interface/class/(boxed) object into an
7868+
// object literal is handled above; spreading anything else (an array,
7869+
// a union, a primitive...) is a real, unimplemented feature (e.g. array
7870+
// spread would need to synthesize numeric-string-keyed fields "0","1",...)
7871+
// rather than an unreachable state - fail with a clear diagnostic instead
7872+
// of crashing until it's implemented.
7873+
emitError(location) << "Spread in an object literal is not supported for type: " << to_print(type);
78647874
return mlir::failure();
78657875
});
78667876

0 commit comments

Comments
 (0)