From 381c01cdf06c23e871a9e4159aa275c097eea0b2 Mon Sep 17 00:00:00 2001 From: cs01 Date: Sun, 26 Apr 2026 11:50:48 -0700 Subject: [PATCH 1/2] [sema] reject inline as { ... } on opaque source types --- src/semantic/type-assertion-checker.ts | 33 +++++++++++++++++++++ tests/fixtures/errors/opaque-inline-cast.ts | 11 +++++++ 2 files changed, 44 insertions(+) create mode 100644 tests/fixtures/errors/opaque-inline-cast.ts diff --git a/src/semantic/type-assertion-checker.ts b/src/semantic/type-assertion-checker.ts index 54b0ebc5..832a5a44 100644 --- a/src/semantic/type-assertion-checker.ts +++ b/src/semantic/type-assertion-checker.ts @@ -254,6 +254,37 @@ class TypeAssertionChecker { } } + private checkOpaqueSource(ta: TypeAssertionNode, fields: InterfaceField[]): void { + const inner = ta.expression as { type: string }; + if (inner.type !== "type_assertion") return; + const innerTa = ta.expression as TypeAssertionNode; + const src = innerTa.assertedType; + if (src !== "unknown" && src !== "any" && src !== "object") return; + + const fieldNames: string[] = []; + for (let i = 0; i < fields.length; i++) { + fieldNames.push(this.stripOpt((fields[i] as InterfaceField).name)); + } + + const output = formatCompileError( + this.sourceCode, + "inline type assertion 'as { " + + fieldNames.join("; ") + + " }' on opaque source (via 'as " + + src + + "') produces wrong GEP indices at runtime", + ta.loc, + "use 'as NamedInterface' instead of inline '{ ... }' when casting from '" + src + "'", + [ + "inline assertion field positions become GEP indices in native code", + "opaque sources have no source interface for the prefix checker to verify against", + "declare a named interface with the correct field layout and cast to that", + ], + ); + process.stderr.write(output); + process.exit(1); + } + private validateInlineAssertion(ta: TypeAssertionNode): void { const assertedType = ta.assertedType; if (!assertedType.startsWith("{")) return; @@ -261,6 +292,8 @@ class TypeAssertionChecker { const parsedFields = this.parseInlineFields(assertedType); if (parsedFields.length < 2) return; + this.checkOpaqueSource(ta, parsedFields); + const assertedNames: string[] = []; for (let i = 0; i < parsedFields.length; i++) { const pf = parsedFields[i] as InterfaceField; diff --git a/tests/fixtures/errors/opaque-inline-cast.ts b/tests/fixtures/errors/opaque-inline-cast.ts new file mode 100644 index 00000000..f52ea405 --- /dev/null +++ b/tests/fixtures/errors/opaque-inline-cast.ts @@ -0,0 +1,11 @@ +// @test-compile-error: inline type assertion +interface Node { + type: string; + value: number; + name: string; +} +function process(x: Node): number { + const y = x as unknown as { type: string; value: number }; + return y.value; +} +console.log(process({ type: "a", value: 42, name: "test" })); From 43f9cd754d8956618da6b9d442a0374c8458e567 Mon Sep 17 00:00:00 2001 From: cs01 Date: Sun, 26 Apr 2026 12:09:09 -0700 Subject: [PATCH 2/2] retrigger ci