Skip to content

Unify declarations and record property presence - #93

Merged
robertvansteen merged 11 commits into
0.xfrom
demanded-inputs
Aug 18, 2026
Merged

Unify declarations and record property presence#93
robertvansteen merged 11 commits into
0.xfrom
demanded-inputs

Conversation

@robertvansteen

@robertvansteen robertvansteen commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • use one RecordType model for expression declarations and nested records
  • make record properties required by default, with new Optional($type) as the explicit omission qualifier
  • keep property presence independent from value absence, so T, Option<T>, Optional<T>, and Optional<Option<T>> remain distinct
  • represent compiler-resolved reads as structural ReferencePath values through programs and diagnostics
  • retain deprecated SymbolSource(name, namespace) and dotted names as migration adapters, including member-access chains rooted in a namespaced symbol
  • resolve structural references through the longest matching definition prefix before projecting remaining members
  • project declarations to only the structural paths a compiled program reads
  • apply that projection recursively at both coerce and assert boundaries, so declared-but-unread nested fields are ignored consistently
  • carry property presence through shapes, relations, analyses, diagnostics, and runtime admission
  • update the README, domain context, changelog, extension guide, and RFCs

Why

The previous proposal introduced a separate Input declaration model and retained flat names such as customer.turnover. That split presence policy from record structure and left declarations behaving differently from every nested record.

This design makes a declaration simply the outer record type. Property presence belongs to the record property; OptionType continues to describe the value domain. Access paths remain structural at compiler interfaces and serialize as {root, properties}; dotted text exists only when a caller requests a human-readable description.

SymbolSource remains deprecated but readable as a migration boundary: its legacy namespace and dotted name are converted to the same structural ReferencePath used by native member access. New code can use structural sources without forcing hosts to rewrite persisted documents in the same release.

Breaking changes

  • remove Input
  • reject empty or dotted member-access properties and dots in canonical ReferencePath and record-property segments
  • expose Program::$references and Diagnosis::$references as list<ReferencePath> instead of dotted strings
  • preserve omitted optional properties as omitted instead of canonicalizing them to null
  • make properties required unless wrapped in Optional
  • bump serialized compilation analysis to version 2 for property-presence metadata

Compatibility

  • deprecated SymbolSource(name, namespace) remains accepted
  • deprecated dotted symbol names remain accepted and are split into structural path segments
  • MemberAccessSource preserves the namespace of a deprecated SymbolSource root

Validation

  • PHPStan: passed
  • PHPUnit: 1,255 tests, 4,820 assertions
  • line coverage: 100% (3,172 / 3,172)
  • Infection: 2,533 mutations, 100% MSI, zero escaped mutants
  • Pint (changed files): passed
  • git diff --check: passed

@robertvansteen robertvansteen changed the title Let a declaration demand a binding its type lets be absent Unify declarations and record property presence Aug 16, 2026
robertvansteen and others added 9 commits August 18, 2026 15:57
A declaration answered two questions from one fact: whether a value may
be absent, and whether the caller must supply one at all. The second was
derived from the first, so an absence-admitting type could always be
omitted — and a host with a "no value" answer could not tell the chosen
answer from no answer yet.

Input splits them. Input::of(Type) is the reading a bare Type already
has; Input::demanded(Type) keeps what the type says about absence and
overrides what it says about supply. Declarations normalize to Inputs at
ingestion, so inference is handed the same Type map as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@robertvansteen
robertvansteen marked this pull request as ready for review August 18, 2026 16:32
@robertvansteen
robertvansteen merged commit 4d5938b into 0.x Aug 18, 2026
6 checks passed
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