Skip to content

Add foundational support for class/record/sealed types - #440

Merged
fabiomadge merged 5 commits into
strata-org:mainfrom
tautschnig:pr/records-sealed-types
Jun 13, 2026
Merged

Add foundational support for class/record/sealed types#440
fabiomadge merged 5 commits into
strata-org:mainfrom
tautschnig:pr/records-sealed-types

Conversation

@tautschnig

@tautschnig tautschnig commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

What was changed?

Adds foundational support for class / record / sealed types to the Java → Laurel front end (JavaToLaurelCompiler). Such types are modelled as opaque Laurel CompositeTypes — Strata treats an unbound composite type as an uninterpreted reference sort — which is enough to accept these values and reason about identity/reference-style properties.

Commits:

  1. Accept and verify class/record/sealed types in parameter position — encode a class/interface type (incl. sealed hierarchies and records) as a CompositeType named after its fully-qualified name ($., package kept for stability/uniqueness). Each referenced sort is declared (an empty compositeCommand, emitted once before the procedures that use it) so Strata's resolver can find it. Test: AcceptClassParam.
  2. new T(...) construction — translate to an opaque new_(T) value. The sort is registered here too, so a type appearing only in new T(...) (never in a type position) is still declared and resolves. Test: NewOnlyRecordType.
  3. instanceof — explicitly rejected with a clear JavaViolationException (the opaque encoding has no runtime tag).
  4. Use fully-qualified names for procedure namesqualifiedMethodName used the outermost class's packageless simple name (Outer_method), inconsistent with the FQ composite-sort scheme; two same-named classes in different packages could collide. Aligned to the FQ scheme (the helper names both declarations and call sites, so they stay matched).

How has this been tested?

./gradlew :verifier:test passes, including AcceptClassParam (type-position acceptance) and NewOnlyRecordType (a record used solely in new position).

Modelling notes / known limitations

Opaque, reference-level foundation — enough for identity/aliasing-style reasoning, not yet a structural encoding:

  • Values are opaque references; record components are not captured (needs a Laurel datatype encoding — future work). Constructor argument values/effects are not modelled.
  • instanceof is not supported and fails fast at translation.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0.

Copilot AI review requested due to automatic review settings June 9, 2026 14:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds initial support for Java class/record reference types in the Java→Laurel translation by treating them as opaque, uninterpreted composite sorts, and introduces minimal handling for new and instanceof expressions.

Changes:

  • Translate Type.ClassType to an opaque CompositeType using the Java type name.
  • Convert new T(...) into an opaque Laurel new_(T) value (discarding constructor args after translation).
  • Convert x instanceof T into a call to an instanceOf_<T>(x) predicate.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@tautschnig
tautschnig force-pushed the pr/records-sealed-types branch from 4bf6ab4 to d9f38b0 Compare June 9, 2026 16:18

@fabiomadge fabiomadge left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locally (origin/main, same Strata e115b8ec): a class/record-typed parameter doesn't verify — static void acceptParam(Point p){}Error: Resolution failed: '...Point' is not defined. The opaque CompositeType is referenced but never declared (no compositeCommand/prelude entry), so Strata's resolver can't find the sort — looks like it needs an actual type declaration emitted.

(1) Is param-position acceptance meant to verify yet, or just translate? (2) No test in the PR, so CI doesn't catch it — one class-typed method would. (Same shape as my #441 note.)

@tautschnig tautschnig changed the title Add foundational support for sealed types Add foundational support for class/record/sealed types Jun 10, 2026
@tautschnig
tautschnig force-pushed the pr/records-sealed-types branch from d9f38b0 to 12f22f6 Compare June 10, 2026 20:55
@tautschnig

Copy link
Copy Markdown
Collaborator Author

@fabiomadge I believe I have addressed your comments with 12f22f6.

@fabiomadge fabiomadge left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update on 12f22f6: the declaration fix works for types in type position (params/locals/returns verify, cross-unit too). But the new path has a gap — a type used only in new T(...), never in a type position, isn't registered, so it still hits the original error:

record Q(int x){}
static void make() { Object o = new Q(1); }
// Resolution failed: '...Q' is not defined

translateType does referencedCompositeTypes.add(sortName); the JCNewClass case forms the same name and calls new_(name) without registering it. One-line fix + a new-only test.

Minor: composite sorts use the FQ dotted name, but qualifiedMethodName uses packageless Outer_name — inconsistent (the FQ scheme is the safer one).

Soundness etc. look good (opaque impure composite = Java reference ==; distinct fresh objects not provably equal; instanceof rejects cleanly). Just the new-path gap.

tautschnig and others added 4 commits June 12, 2026 19:07
…parameter position

Encode a Java class/interface type (including sealed hierarchies and
records) as an opaque Laurel CompositeType named after the type's
fully-qualified name (with `$` normalised to `.`). Keeping the package
makes the sort name stable and collision-free.

Crucially, each referenced composite sort is now *declared* — an empty
compositeCommand emitted (once, before the procedures that use it) so
Strata's resolver can find the sort. Without the declaration a
class/record-typed parameter failed with "Resolution failed: '...' is
not defined" rather than verifying.

Adds AcceptClassParam, a test with a record-typed parameter method that
verifies cleanly.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
…d types

Translate `new T(...)` to an opaque Laurel `new_(T)` value of the matching
CompositeType. Constructor arguments are translated (so unsupported
argument expressions still surface as errors) but otherwise discarded: the
opaque value models reference identity only, not the constructor's
arguments or their side effects.

The composite sort is registered for declaration here too, so a type that
appears only in `new T(...)` (and never in a type position such as a
parameter, local, or return) still gets its sort declared and the
`new_(T)` value resolves.

Adds NewOnlyRecordType, a test for a record used solely in `new` position.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
…h a clear error

In the opaque-CompositeType encoding a class/record value carries no
runtime tag, so `r instanceof X` cannot be modelled precisely. Fail at
translation with a targeted JavaViolationException attributable to the
`instanceof` feature, instead of emitting an undeclared `instanceOf_<X>`
predicate symbol — which surfaced only as a confusing downstream
"Resolution failed" message and, using the simple type name, could even
collide across packages. A precise encoding needs a tagged datatype
representation (future work).

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
qualifiedMethodName used the outermost class's packageless simple name
(Outer_method), while composite sorts use the fully-qualified dotted
name. Two same-named classes in different packages could therefore
produce colliding procedure names. Align procedure names with the
composite-sort scheme: use the outermost class's fully-qualified name
(with '$' normalised to '.'). The name is used for both the procedure
declaration and its call sites (via the same helper), so they stay
matched.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
@tautschnig
tautschnig force-pushed the pr/records-sealed-types branch from 12f22f6 to d4abe13 Compare June 12, 2026 19:30
@tautschnig

Copy link
Copy Markdown
Collaborator Author

@fabiomadge I believe all your comments have been addressed.

@fabiomadge fabiomadge left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both my findings are fixed and verified: the composite sort is now declared, and the new-only-type gap is closed (referencedCompositeTypes.add(name) in the JCNewClass case) with a NewOnlyRecordType regression test — my repro verifies now. The qualifiedMethodName FQ-rename (addressing the naming-consistency note) passes the full verifier suite with no regressions.

Two non-blocking notes for the record: (1) records are modeled as opaque references — sound for identity/==, but component/value reasoning is deferred (documented); (2) the FQ method-name change is broad — I validated it via the full suite, but didn't exhaustively probe overloaded-name collisions (the existing TODO in that method still applies). Sound foundation. LGTM.

@fabiomadge
fabiomadge merged commit e9c9cd4 into strata-org:main Jun 13, 2026
2 checks passed
@tautschnig
tautschnig deleted the pr/records-sealed-types branch June 14, 2026 08:07
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.

3 participants