Skip to content

fix(patterns): preserve literal inference in compound matchers - #3335

Open
kriskowal wants to merge 1 commit into
masterfrom
kriskowal-patterns-literal-inference
Open

fix(patterns): preserve literal inference in compound matchers#3335
kriskowal wants to merge 1 commit into
masterfrom
kriskowal-patterns-literal-inference

Conversation

@kriskowal

Copy link
Copy Markdown
Member

Description

The @endo/patterns matcher declarations preserve literal discriminants and tuple shapes through M.or, M.and, M.splitArray, and M.splitRecord. This keeps TypeFromPattern and interface method-guard inference narrow enough for implementations that use discriminated inputs.

The declarations cover required, optional, and rest pattern positions. Const type parameters retain literal information at the generic boundary, while type inference removes only the readonly modifier introduced for object literals so matched records keep their existing mutable public shape. CastedPattern<T> remains available for unverifiable branded or cross-field claims, but is no longer needed for literals the reusable API can preserve soundly.

Security Considerations

This declaration-only change introduces no new authority or security boundary and does not alter runtime matching.

Scaling Considerations

This change does not affect runtime CPU, memory, storage, or message use.

Documentation Considerations

The narrower inferred types are backward compatible for callers that do not rely on widened inference. No documentation or upgrade action is required.

Testing Considerations

Type regressions cover literal unions, record discriminants, tuple and rest patterns, intersections, and a nested interface method guard. The @endo/patterns package lint passes.

Compatibility Considerations

Existing runtime behavior and mutable matched-record result shapes remain unchanged. TypeScript consumers gain narrower, more accurate inference for compound literal patterns.

Upgrade Considerations

No live-system upgrade considerations apply.

Pattern combinators previously inferred their generic arguments through broad Pattern arrays and CopyRecord constraints. TypeScript therefore widened object literal discriminants before TypeFromPattern could inspect them, which propagated broad method parameters through interface guards and makeExo.

Use const type parameters for M.or, M.and, M.splitArray, and M.splitRecord, including their optional and rest pattern parameters, so literal unions, record fields, and tuples survive inference. TypeFromPattern removes only the readonly modifier introduced by const object inference when producing matched record values, preserving the existing mutable public result shape.

CastedPattern<T> remains useful for unverifiable branded or cross-field claims, but it is an unchecked escape hatch rather than the remedy for literals that the API can preserve soundly.

The focused type regressions cover raw literal unions, discriminants in splitRecord, literal tuples and rest patterns in splitArray, intersections through M.and, and a nested interface method guard. The change is declaration-only and does not alter runtime matching or compatibility for callers that do not rely on widened inferred types.
@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 8322547

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Comment on lines +63 to +66
? // Const type parameters preserve object literals as readonly, but
// TypeFromPattern describes matched values using the existing mutable
// record shape.
Simplify<{ -readonly [K in keyof P]: TypeFromPattern<P[K]> }>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Endo patterns only apply to immutable data; shouldn't this force readonly?

Suggested change
? // Const type parameters preserve object literals as readonly, but
// TypeFromPattern describes matched values using the existing mutable
// record shape.
Simplify<{ -readonly [K in keyof P]: TypeFromPattern<P[K]> }>
? // An immutable CopyRecord can be described by a mutable template.
Simplify<{ readonly [K in keyof P]: TypeFromPattern<P[K]> }>

type TFSplitRecord<Req, Opt, Rest = never> = Simplify<
(Req extends CopyRecord<any>
? { [K in keyof Req]: TypeFromPattern<Req[K]> }
? { -readonly [K in keyof Req]: TypeFromPattern<Req[K]> }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
? { -readonly [K in keyof Req]: TypeFromPattern<Req[K]> }
? { readonly [K in keyof Req]: TypeFromPattern<Req[K]> }

: {}) &
(Opt extends CopyRecord<any>
? { [K in keyof Opt]?: TypeFromPattern<Opt[K]> }
? { -readonly [K in keyof Opt]?: TypeFromPattern<Opt[K]> }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
? { -readonly [K in keyof Opt]?: TypeFromPattern<Opt[K]> }
? { readonly [K in keyof Opt]?: TypeFromPattern<Opt[K]> }

Comment on lines +241 to +242
* Const type parameters preserve object literals as readonly, but matched
* record values retain the existing mutable shape.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
* Const type parameters preserve object literals as readonly, but matched
* record values retain the existing mutable shape.

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.

2 participants