Skip to content

A field of a union of same-layout object types is read from the base type - #384

Merged
ASDAlexander77 merged 1 commit into
mainfrom
union-of-object-types-property-access
Sep 28, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
union-of-object-types-property-access

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Reading a field through a union of object types that share one layout failed verification:

type Shape = { kind: "circle", r: number } | { kind: "square", r: number };
function size(s: Shape) { return s.r; }
// error: 'ts.PropertyRef' op operand #0 must be any const tuple reference type or any tuple
//        reference type ..., but got '!ts.ref<!ts.union<!ts.tuple<…>, !ts.tuple<…>>>'

It also broke s.kind === "circle" and s.r !== null on such a union. Unions of interfaces were not affected. Found while reviewing #231.

Cause

A union whose members share one layout needs no tag (isUnionTypeNeedsTag returns false) and is stored as its base type. mlirGenPropertyAccessExpressionBaseLogic switched to that type (actualType = baseType), but two things still referred to the union:

  • the value being accessed was left as the union;
  • MLIRPropertyAccessCodeLogic still held the original expression, which was the Load of the union variable.

The tuple accessor took the reference behind that load (getExprLoadRefValue) and built a PropertyRef on ref<union>.

Fix

When the union collapses to its base type:

  • the value is cast to that base type;
  • the access logic is given the cast value through a new MLIRPropertyAccessCodeLogic::setExpression.

The field is then extracted from a value of the base type.

Tests

  • New 00union_object_types_access.ts (compile, jit, and the rc/none corpus). It covers a discriminant test, a field read, and a !== null check on a nullable field of such a union.
  • Full release suite: 2993/2993 passed.
  • TypeScriptCompilerDefaultLib tests, release jit and compile: 156/156 each. The cast applies to every untagged union collapsed here, not only object types.

Not in this PR

With members of different layouts, if (s.kind === "circle") return …; return s.w; still fails. That needs the early-exit narrowing in #383. Narrowing inside the if already works.

🤖 Generated with Claude Code

…type

`s.kind` / `s.r` on `s: { kind: "circle", r: number } | { kind: "square", r:
number }` failed verification: "'ts.PropertyRef' op operand #0 must be ...
tuple reference type ... but got '!ts.ref<!ts.union<...>>'". Such a union
needs no tag and is stored as its base type, and property access switched to
that type (actualType = baseType), but the value it accessed stayed the union,
and MLIRPropertyAccessCodeLogic still held the original expression: the tuple
accessor read the field through the union's reference.

The value is now cast to the base type, and the access logic is given it
(MLIRPropertyAccessCodeLogic::setExpression). Found in the #231 review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 force-pushed the union-of-object-types-property-access branch from 4c2bf05 to 1532cb2 Compare September 28, 2026 08:33
@ASDAlexander77
ASDAlexander77 merged commit 80a733b into main Sep 28, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the union-of-object-types-property-access branch September 28, 2026 08:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant