Skip to content

Derived-type semantics: subprogram visibility, discrete subtypes, multidim arrays, and access types (RM 3.4)#44

Open
AdaDoom3 wants to merge 24 commits into
mainfrom
claude/jolly-thompson-m6q85l
Open

Derived-type semantics: subprogram visibility, discrete subtypes, multidim arrays, and access types (RM 3.4)#44
AdaDoom3 wants to merge 24 commits into
mainfrom
claude/jolly-thompson-m6q85l

Conversation

@AdaDoom3

@AdaDoom3 AdaDoom3 commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Completes five ACATS derived-type groups to 100%, implementing the Ada 83 semantics of RM 3.4 (derived types) and the visibility, derivability, allocation, and dereference rules they depend on. Every fix is upstream of the expander (semantics / type construction), grounded in the RM and GNAT's model.

Test results (full suite, this branch vs. base f861211)

Suite Base This PR
Class A (acceptance) 140 / 140 140 / 140 (100%)
Class C (executable) 1182 / 1976 (59%) 1212 / 1976 (61%)

Per-test diff over the full 1976-test Class C suite: 0 regressions (no test passing at base fails or skips now) and +30 net passes (33 newly passing, run-to-run tasking noise accounts for the small difference).

Groups driven to 100%

Group Before → After Feature
c34014 2/14 → 14/14 derived subprogram & operator visibility (RM 3.4 + 8.3)
c34001 3/4 → 4/4 derived enumeration subtypes
c34016 0/1 → 1/1 (same root cause as c34001)
c34005 13/14 → 14/14 derived multidimensional arrays
c34007 3/11 → 11/11 derived access types

Collateral improvements with no regressions: c34 40→62, c41 36→38, c83 24→26, c87 45→47.

Key changes

RM 3.4 / 8.3 — derived subprogram visibility (c34014)

  • Homograph hiding (Subprograms_Are_Homographs, Subprogram_Is_Implicit): in Symbol_Add, an explicit subprogram hides an implicitly declared (derived) homograph in the same region, in either declaration order — mirroring GNAT's visibility-based hiding on the homonym chain.
  • Per-overload visibility: Symbol_Find, Symbol_Find_By_Type, and Collect_Interpretations check visibility per overload, not just at the bucket head, so a hidden homograph at the chain head no longer masks visible overloads. Symbol_Find_By_Type prefers a same-named-type match over a loose Type_Root match.
  • First-/second-kind derivability (declared_in_visible_part, Subprogram_Is_Derivable, Has_Visible_Part_Explicit_Homograph): Derive_Subprograms scans the parent type's declarative region (seeing implicit ops the export list omits) and keeps exactly the operations a derived type inherits — a visible-part explicit homograph overrides the inherited op; a private-part/body/block homograph is not derivable, so the inherited op is re-derived.
  • Body sees the spec's implicit ops (Install_Derived_Operations): re-installs implicit derived ops from the spec scope into the body scope (walking buckets + overload chains, since symbols[] records only chain heads), preserving each op's parent for mangling.
  • Ultimate_Operation interleaves parent_operation and rename peels (GNAT Ultimate_Alias); a generic instance exposes its actual profile, not the formal's renamed view (Peel_Generic_Actual_View); unary user-operators receive their context type; a USE-view of a renaming/inherited op carries the links to dispatch to the same body; visible-part renamings and instantiations are exported (Decl_Is_Visible_Subprogram).

Derived discrete subtypes (c34001, c34016)

  • Deriving from a constrained discrete subtype introduces an unconstrained base T'BASE; inherited enumeration literals are values of that base, so an out-of-range literal assigned to the subtype is range-checked and raises CONSTRAINT_ERROR.

Derived multidimensional arrays (c34005)

  • Wrap_Constrained_As_Fat wraps a multidimensional array with every dimension's bounds (was dim 0 only), so array equality (RM 4.5.2) against an aggregate/conversion no longer reads garbage inner-dimension lengths.

Derived access types (c34007)

  • Slice a function-call result; slice-assign through an access-to-array; whole-array .ALL := sized from the access bounds; P'RANGE on access-to-array dereferences implicitly (RM 4.1).
  • Allocate and bound-check every dimension of a multidimensional designated array, sizing the heap by the product of all dimensions for static and dynamic bounds (RM 4.8); a positional aggregate of a dynamic-bounds constrained array evaluates its constraint low rather than the index subtype's first.
  • Bind a constrained discriminant for dependent component default bounds (RM 3.7.1); membership in a constrained access-to-record subtype checks the designated discriminants (RM 3.3.2).
  • Null-check a .ALL dereference taken as an address (RM 4.1); a .ALL-target aggregate is typed against the unconstrained base designated so base-range values reached through inherited operations are matched to the run-time object, not the static constraint (RM 3.4).
  • An inner access object hides an outer homograph subprogram of the same name (RM 8.3).

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t

claude added 13 commits June 9, 2026 17:07
Make derived subprograms and operators correctly visible, hidden, and
further-derivable, matching GNAT's model (Alias / Ultimate_Alias for
inherited bodies, visibility-based hiding on the homonym chain, and the
RM 3.4 first-/second-kind derivability distinction).

Core changes, all upstream of the expander:
- RM 8.3 hiding of an implicit (derived) homograph by an explicit one,
  in either declaration order (Symbol_Add).
- Per-overload visibility in the lookups that walk overload chains
  (Collect_Interpretations, Symbol_Find, Symbol_Find_By_Type); the last
  prefers a same-named-type match over a loose Type_Root match.
- RM 3.4 derivability: Derive_Subprograms scans the parent type's
  declarative region and keeps exactly the operations a derived type
  inherits (visible-part explicit homograph overrides; private/body/block
  homograph is not derivable, so the inherited op is re-derived).
- The body re-installs the spec's implicit derived ops; a forward spec
  never "completes" an inherited op.
- Ultimate_Operation interleaves parent_operation and rename peels.
- Visible-part renamings and subprogram instantiations are exported and
  derivable; a subprogram instance exposes its actual profile rather than
  the formal type's renamed view.
- Unary user-operator resolution receives its context type; a USE view of
  a renaming/inherited op dispatches to the same body.

c34014: 2/14 -> 14/14. No suite regressions; c83 24->26, c87 45->46.

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
…100%)

Deriving from a constrained discrete subtype (e.g. `type S is new SUBPARENT`)
must introduce an unconstrained base S'BASE holding every value of the parent
base type, while S itself carries the constraint (RM 3.4). Inherited
enumeration literals are values of that base and may fall outside S's range.

They were typed with the constrained first subtype, so an assignment of an
out-of-range literal hit the "source subtype fits target subtype" elision in
the scalar range check (source == target trivially fits) and skipped the
check, failing to raise CONSTRAINT_ERROR.

NK_DERIVED_TYPE now builds a distinct unconstrained base for a derived discrete
type whose parent is a constrained subtype (mirroring the array/record/access/
fixed base construction), and inherited enumeration literals are typed with
that base.

c34001 3/4 -> 4/4; c34016 0/1 -> 1/1; c34 51 -> 53. No suite regressions.

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
… (c34005 100%)

Comparing a multidimensional constrained array against a value routed through
the fat-pointer equality path (an aggregate, or an array type-conversion
result) returned the wrong answer: Wrap_Constrained_As_Fat built the fat
pointer from dimension 0's bounds only, but Generate_Array_Equality reads every
dimension's bounds per RM 4.5.2, so the inner dimensions' lengths were
undefined and equal arrays compared unequal.

Wrap_Constrained_As_Fat now wraps a multidimensional array with every
dimension's bounds via Emit_Fat_Pointer_For_Lvalue, matching the sibling
fat-wrap path. One-dimensional arrays keep the existing path with its aggregate
positional-count adjustment.

c34005 13/14 -> 14/14; c34 53 -> 54. No suite regressions.

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
… (RM 4.1)

Two codegen gaps around slicing with an implicit access dereference:

- A parameterless function whose result is (an access to) an array, written
  F(...), was treated as a call passing the slice/index as an argument, so the
  range reached Generate_Expression as an unsupported node. Resolve_Apply now
  recognises F(...) as an index or slice of the implicitly called result
  (RM 4.1, 4.1.3), and the indexed-component emitter calls a function prefix
  rather than loading it as object storage.

- A slice assignment X(L..H) := S where X is access-to-array did not
  dereference X, so the array-target path was skipped and the range fell
  through to the scalar path. The slice-assignment target now implicitly
  dereferences an access-to-array prefix (the access value is the fat pointer
  for an unconstrained/dynamic designated array); element assignment keeps the
  existing general lvalue path.

Fixes the c34007d internal error. No suite regressions.

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
…s (RM 3.7.1)

When a discriminated record is created with a discriminant constraint (e.g. an
allocator NEW DESIGNATED(TRUE, 3), or a constrained subtype), the discriminant
components were skipped entirely during default initialisation because their
values come from the constraint, not their defaults. That also skipped binding
the discriminant for dependent component bounds, so a component default such as
S : STRING (1 .. L) := (1 .. L => 'A') referenced L as an unbound discriminant
symbol — an undefined value at link time.

Emit_Apply_Component_Defaults now binds each constrained discriminant to its
field address in the record (where the constraint value already lives) before
the dependent components are initialised, so L resolves through the record.

Fixes the link failures in c34007s / c34007u. No suite regressions.

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
…ants (RM 3.3.2)

X IN T where T is an access subtype constraining its designated record's
discriminants (type T is new ACC(TRUE, 3)) always returned TRUE — only
access-to-array subtypes checked the designated constraint. A non-null access
value now belongs iff its designated record's discriminants equal the
subtype's constraint; a null value belongs.

Fixes c34007r and c34007u.

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
…ated array (RM 4.8)

Allocating a multidimensional array through an access type with a qualified
aggregate (NEW SUBDESIGNATED'((1,2,3),(4,5,6))) had three multi-dim gaps:

- the heap byte size counted only the first dimension's length, under-allocating
  the data;
- the result fat pointer carried only dimension 0's bounds, so a later check or
  comparison read garbage for the inner dimensions;
- the object-declaration access constraint check looped over dimensions but read
  dimension 0's bounds each time (Emit_Fat_Pointer_Low/High instead of the
  per-dimension _Low_Dim/_High_Dim), so a value matching the first dimension was
  checked against every dimension's constraint and raised a spurious
  CONSTRAINT_ERROR.

The qualified-aggregate allocator path now sizes the allocation by the product
of all dimensions, builds a multi-dim heap bounds struct, and checks each
dimension against the index subtype and the target access subtype; the access
constraint check reads each dimension's actual bounds.

Fixes c34007i; advances c34007g past elaboration. No suite regressions.

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
…or dynamic bounds too

The previous multi-dim allocation fix sized the data only on the static-bounds
path. A designated array with dynamic bounds (NEW SUBDESIGNATED'(...) where
SUBDESIGNATED is DESIGNATED(IDENT_INT(4)..IDENT_INT(5), ...)) took the fat
initializer branch, whose length is the first dimension's element count, so the
allocation was short and every dereference read wrong data.

The allocator now overrides the byte length for any multidimensional designated
with the runtime product of all dimensions' lengths (evaluated from the
initializer type's bounds, static or dynamic) times the element size, before
the heap allocation and copy.

Advances c34007g (initialization, conversions, and .ALL value now correct). No
suite regressions.

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
…atic type (RM 5.2)

Assigning to X.ALL where X designates an unconstrained or dynamically bounded
array copied designated->size bytes — for an unconstrained array that static
size is just one element, so only the first element was written. The whole-array
.ALL assignment now derives the byte size from the access value's fat-pointer
bounds (the product of every dimension's length times the element size).

Advances c34007g (.ALL and indexed assignments now correct).

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
…straint (RM 4.1)

X'RANGE used as an array index constraint (Y : DESIGNATED (X'RANGE, 1 .. 3))
returned no bound when X is an access-to-array, because the prefix type is an
access, not an array. It now dereferences the access to the designated array,
so X'RANGE and X'RANGE(N) denote the designated array's dimension bounds.

Completes c34007g.

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
Generate_Composite_Address returned the access value for X.ALL without a null
check, so dereferencing a null access in a context that takes its address — a
record/array comparison, a component or element of X.ALL — read through the
null pointer and crashed instead of raising CONSTRAINT_ERROR. The .ALL address
path now emits the access check (which handles both thin and fat access values).

Fixes c34007m and c34007s.

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
…d (RM 3.4)

X.ALL := aggregate where X is a constrained access (type T is new ACC(TRUE,3))
typed the source aggregate with T's constrained designated, so a base-range
value — e.g. (FALSE, 2, ...) reached through an inherited operation that yields
an object outside the subtype's constraint — was checked against the static
constraint and raised a spurious CONSTRAINT_ERROR. The .ALL assignment denotes
the actual object, so the source is now typed against the unconstrained base
designated and matched to the run-time object, not the constraint.

Fixes c34007p.

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
…objects shadow outer subprograms (c34007 100%)

Two fixes completing c34007 (derived access types):

- A positional aggregate of a constrained array whose bounds are dynamic
  (NEW SUBDESIGNATED'(1,2,3) where SUBDESIGNATED is DESIGNATED(IDENT_INT(5)..
  IDENT_INT(7))) fell back to the index subtype's first (0), producing 0-based
  bounds that failed the constrained access target's RM 4.8 bound check. It now
  evaluates the dynamic constraint low and derives the high from the element
  count.

- RM 8.3: an inner object whose type is access-to-array now hides an outer
  homograph subprogram of the same name, so A(I) inside a body with a local
  access variable A and an enclosing procedure A indexes the variable (through
  an implicit dereference) instead of referencing the procedure's frame.

c34007: 10/11 -> 11/11.

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
@AdaDoom3 AdaDoom3 changed the title RM 3.4 & 8.3: derived subprogram visibility and derivability Derived-type semantics: subprogram visibility, discrete subtypes, multidim arrays, and access types (RM 3.4) Jun 10, 2026
claude added 11 commits June 10, 2026 18:01
Array assignment requires equal lengths per dimension (the value slides, but the
lengths must match); a mismatch raises CONSTRAINT_ERROR. Two gaps:
- a whole constrained array assigned from another constrained array of different
  length was memcpy'd with no length check;
- a slice target assigned from a source slice did not compare the two slice
  lengths.
Both now emit a per-dimension length check (static bounds fold to a constant
comparison, dynamic bounds check at run time).

Advances c52104 1/14 -> 3/14.

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
A slice target assigned from a non-slice source (a string literal, a whole
constrained array, or an array value) now checks the source length against the
slice length and raises CONSTRAINT_ERROR on a mismatch.

Advances c52104 3/14 -> 5/14.

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
X(a..b)(c..d) := source crashed codegen (a range reached Generate_Lvalue as an
expression). A slice keeps the original index values, so the outer range is
already absolute; the target is collapsed to a single slice on the base array,
which the slice-assignment path handles directly (including the length check).

Advances c52104 5/14 -> 7/14 (all subtests now compile).

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
A slice of a dynamically bounded array (stored as a fat pointer) assigned from a
string/array source did not compare lengths. The fat slice-assignment path now
checks the source length against the slice length and raises CONSTRAINT_ERROR on
a mismatch, like the constrained path.

Advances c52104 7/14 -> 11/14.

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
…zer's (RM 3.6)

An object A : T(2..6) := "QUINC" with dynamic constraint bounds stored the
string initializer's bounds (1..5) instead of the subtype's (2..6), so a later
slice A(2..6) raised a spurious 'slice bound outside array' CONSTRAINT_ERROR. A
constrained array object now keeps its own subtype bounds (the initializer
slides into them); an unconstrained object still takes the initializer's bounds.

Advances c52104 11/14 -> 13/14 (fixes c52104l, c52104m).

https://claude.ai/code/session_01C2sPU3U9ejM9DgqkYwkj7t
A scalar membership test X IN T is statically TRUE when X's nominal
subtype is provably covered by T, since every value of that subtype
belongs to T. Folding it removes the runtime range check, which was
reading the operand even when it had not yet been assigned a value -
making a test of an own-subtype membership depend on stack garbage.
This mirrors GNAT's Compile_Time_Compare, which proves the bounds from
the operand's nominal subtype rather than its runtime value.
An array type conversion now:
 - rebuilds the fat pointer at the target index type's bound width, so an
   operand whose index type is narrower (e.g. INT -100..100 in i8) is read
   correctly through an INTEGER-indexed view;
 - for a constrained target, imposes the target subtype's bounds and length-
   checks each dimension;
 - for an unconstrained target, checks that each non-null dimension's bounds
   belong to the target's index subtype;
 - checks that the operand and target component subtypes carry the same
   constraint (scalar range, real accuracy, nested array bounds, access /
   record discriminants), raising CONSTRAINT_ERROR when they differ.

Fixes c46041a, c46042a, c46043a, c46043b, c46044a, c46044b.
A subprogram given as a forward spec in a declarative part now carries a
module-global elaboration flag, reset false where the spec elaborates and
set true where the body elaborates. Calls - both parametered and
parameterless - check the flag and raise PROGRAM_ERROR when the body has
not yet run, e.g. a function called in the initialization of an object
declared before the body. The flag is global so a call reached from a
generic instance or a default expression in another frame still sees it.
A generic unit that has a body now carries the same elaboration flag as a
subprogram: reset false at the generic declaration, set true where its body
elaborates, and checked at each instantiation. Instantiating before the body
elaborates raises PROGRAM_ERROR. Bodyless generic packages have nothing to
elaborate and are left unflagged, so a normal instantiation of one does not
spuriously raise.
An object of an unconstrained discriminated type with defaults reports
'CONSTRAINED based on whether it can be re-discriminated. An IN formal
parameter and a constant object never can, so 'CONSTRAINED is always TRUE
for them regardless of the nominal subtype.
A heap object's discriminants are fixed when it is allocated, so a .ALL
dereference always yields a constrained object. X.ALL'CONSTRAINED now
returns TRUE regardless of whether the designated subtype is written with
a constraint. Fixes c37404a.
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