Accept Behavior Annex loops without an iterator classifier 🤖 - #3227
Merged
Merged
Conversation
AS5506/3 Rev A D.6 writes the iterator of a for and of a forall construct as element_identifier [ : data_unique_component_classifier_reference ], so the classifier is optional, and the standard's own BA_example4 omits it. The grammar requires it, so every loop that leaves it out is a syntax error. Issue3173.aadl writes both constructs without a classifier over each of the three element_values the production admits: an integer range, an input event data port, and a data component array in both the AADL core and the Data Model spelling. A written classifier is kept as the positive control. Each loop body names its iterator as the first operand of an arithmetic expression, because the applicable operator is selected by the type of that operand alone; an iterator left without a type is therefore reported even while the conformance check between the two operand types is disabled. That is what the pre-fix baseline of NonDataIteratorClassifier.aadl records beside the existing "iterative variable data classifier is not resolved" finding. The assertions require the strict model to carry the type the iterated values determine rather than an unresolved iterator: an integer range makes the iterator a universal integer with no classifier, the way a port count value is one, while a port and an array each supply the classifier of their own elements, which for a Data Model array is the array's base type. Reads of the iterator inside the loop must carry that same type, and a copy of the annex must serialize the classifier-free loops and reparse to the same types. NonDataIteratorClassifier.aadl fixes the other side: a written classifier of a category other than data must stay rejected, so that making the classifier optional cannot turn a rejected declaration into one whose type is inferred from the iterated values instead. Activate the existing conformance expectation for the two optional-classifier fixtures while preserving the pre-fix corpus baselines. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AS5506/3 Rev A D.6 writes the iterator of a for and of a forall construct as element_identifier [ : data_unique_component_classifier_reference ], and the standard's own BA_example4 omits the classifier. Make the colon and classifier optional in the grammar, and give an iterator that has none the type D.6's semantics give it: an integer range makes it an integer, an input event data port makes it the port's data classifier, and a data component array makes it the classifier of the array's elements, which for a Data Model array is the array's base type. Translation resolves that classifier from the iterated values, the way it resolves every other name in the annex, and before the loop body, because a data element reached through the iterator needs the iterator's own type to resolve. The strict model therefore carries the type, and getClassifier, the type holder, the data representation, and the resolver's own data-element lookup all read the one field instead of each inferring it. An integer range is the exception, having no classifier to name: its universal integer comes from the loop, exactly as a port count value's does. Resolving it on demand against the container of each caller was considered and rejected. The answer does not vary with the component that asks: the Data Model values and the constraining classifier of an unbound prototype are read from classifiers, and every path hands the checker the annex's own owner. The declarative limits are unchanged either way, and are pre-existing, since the same lookup already types the iterated values of a loop whose classifier is written. The strict type check that the iterator has a resolved data classifier could no longer distinguish an omitted classifier from a written one of another category, and resolution would have silently supplied a type for the second. It now reports only an iterator that ends up with no type, and the requirement that a written classifier be a data classifier moves to the declarative validator, which reports it on the reference as written and gates the strict checkers the way the other unrepresentable spellings do. That replaces "iterative variable data classifier is not resolved" for this case with a message naming the classifier and the rule. Regenerate the runtime and content-assist grammars, refresh the four affected characterization fixtures across the diagnostics, positions, and resolved-model goldens, and update the conformance report. No metamodel change was needed: a single-valued assignment is already optional in the derived Ecore. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lwrage
force-pushed
the
3173_optional_loop_iterator_classifier
branch
from
September 14, 2026 19:53
a286891 to
8845f3a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3173
Cause and correction
AS5506/3 Rev. A D.6 writes the iterator of a
forand of aforallconstruct aselement_identifier [ : data_unique_component_classifier_reference ], and the standard's own BA_example4 omits theclassifier (
for(d in int_set)).BehaviorAnnex.xtextrequired the colon and the classifier, so every loop that leftthem out was a syntax error. The colon and classifier are now an optional group.
An iterator without a written classifier takes the type D.6's semantics give it: an integer range makes it an integer,
an input event data port makes it the port's data classifier, and a data component array makes it the classifier of the
array's elements, which for a Data Model array is the array's base type.
Translation resolves that classifier from the iterated values, the way it resolves every other name in the annex, and
before the loop body, because a data element reached through the iterator (
entry.left) needs the iterator's own typeto resolve. The strict model carries the type, so
getClassifier, the type holder, the data representation, and theresolver's data-element lookup all read the one field rather than each inferring it. An integer range is the exception,
having no classifier to name: its universal integer comes from the loop, exactly as a port count value's does.
Resolving it on demand against each caller's container was considered and rejected. The answer does not vary with the
component that asks:
Data_RepresentationandBase_Typeare read from the data classifier of the iterated element,the constraining classifier of an unbound prototype is read from the prototype, and every path hands the checker the
annex's own owner. The declarative limits below are the same either way, and are pre-existing.
Two checks moved as a consequence.
AadlBaTypeChecker.checkForOrForAllcould no longer tell an omitted classifier froma written one of another category, and resolution would have silently supplied a type for the second; it now reports only
an iterator that ends up with no type at all. The requirement that a written classifier be a data classifier moved to
BehaviorAnnexValidator.checkIteratorClassifiers, which reports it on the reference as written and gates the strictcheckers the way the other unrepresentable spellings do. For that case the diagnostic changes from
iterative variable data classifier is not resolvedon the whole loop to'helper' is not a data classifier: a for or forall iterator can only name a data component classifieron theclassifier reference.
No metamodel change was needed: a single-valued assignment is already optional in the derived Ecore, so the
regeneration touched only the runtime and content-assist parsers, the grammar access, the semantic sequencer, and the
.xtextbin.Regression model and assertions
ba/org.osate.ba.tests/models/issue3173/Issue3173.aadlwrites both constructs without a classifier over each of thethree
element_valuesthe production admits — an integer range, an input event data port, and a data component array inboth the AADL core (
history : data Base_Types::Integer [3]) and the Data Model (pairs : Pairs) spelling — and keeps awritten classifier as the positive control. Every loop body names its iterator as the first operand of an arithmetic
expression, because the applicable operator is selected by the type of that operand alone; an iterator left untyped is
therefore reported even while the conformance check between the two operand types is disabled (#3199). The pre-fix
baseline of the second fixture records exactly that:
There is no applicable operator "+" for type unknown.Issue3173Testasserts:AadlBaUtils.getTypeHolderreports it:step -> Integer <no classifier>,sample -> Integer Base_Types::Integer,element -> Integer Base_Types::Integer,entry -> Struct Issue3173::Pair,and the written control
typed -> Integer Base_Types::Integer;entry.leftandentry.right;types, which is what exercises the regenerated sequencers rather than the retained node model.
NonDataIteratorClassifier.aadlholds the other side:for (step : helper in 0 .. 2)andforall (other : helper.impl in 0 .. 2)name a thread type and a thread implementation and must stay rejected, so thatmaking the classifier optional cannot turn a rejected declaration into one whose type is resolved from the iterated
values instead.
The conformance expectation
optionalLoopClassifierIsAcceptedByTheStandard(G09) is activated in the regression commitand passes with the fix, so
OptionalForClassifier.aadlandOptionalForallClassifier.aadlare now clean.Validation
Run outside the sandbox, from the repository root.
-Dtest='Issue3173Test,BehaviorAnnexConformanceTest'—Issue3173Test5 tests, 5failures, each on the syntax error at the omitted colon or on the pre-fix diagnostics of the second fixture;
BehaviorAnnexConformanceTest1 failure on the activated expectation.Issue3173Test5 tests, 0 failures;BehaviorAnnexConformanceTest12 tests, 0failures, 1 skip (
unaryPlusIsRejectedByTheStandard, Reject unary plus in Behavior Annex expressions 🤖 #3170).-Dosate.ba.tests.regenerate=true -Dtest='BehaviorAnnexCharacterizationTest,BehaviorAnnexValidatedDiagnosticsTest',then rerun without the flag. Only the four affected fixtures changed, across
diagnostics,diagnostics-validated,positions, andresolved-model; nounparsegolden and no other corpus model moved. Asecond regeneration after the change described above under "considered and rejected" produced no golden churn at all,
which is the check that where the classifier is resolved is not observable in the recorded models.
-Dtycho.localArtifacts=ignore: BUILD SUCCESS in 2:13, no failures or errors anywherein the reactor.
git diff --checkclean apart from the trailing whitespace the Xtext generator itself emits intosrc-gen, which is left as generated.Dependencies and merge order
None. The branch is based on
0def9cf962, which is the current tip ofmaster, and it does not depend on any unmergedPR.
Residual risk
loop whose classifier is written, so making the classifier optional inherits them rather than introducing them.
Data_RepresentationandBase_Typeare looked up on the data classifier of the iterated element, so an associationon the element itself is not consulted, contained or not. Iterated values typed by a prototype resolve to the
prototype's constraining classifier: a legal model that declares
buffer : data element_type [3]underelement_type : data Base_Types::Integerand bindselement_type => data Base_Types::Floaton one subcomponent andBase_Types::Integeron another validates with no diagnostic, and the iterator is typedBase_Types::Integerforboth, because a prototype bound on a subcomponent is not reachable from the classifier the checker is given. Closing
that needs an instance-context resolution, which is the part of Allow omitted classifiers in Behavior Annex for and forall loops 🤖 #3173's comment that remains open and is recorded in
the G09 row of
ba/doc/conformance.md.AdaLikeDataTypeChecker.conformsToreturnstrueunconditionally (Behavior Annex type conformance is disabled, so no type mismatch is reported 🤖 #3199). The iterator types this PR establishes arewhat that check will compare once it is enabled, so the fixtures are chosen to be correct under it.
ClassCastException, which I confirmed at0def9cf962with written classifiers and filed as Behavior Annex for and forall crash when the iterated values name a loop iterator 🤖 #3226. It isindependent of this change and out of scope here.
checkIteratorClassifiersgates the strict checkers for a subclause with a non-data iterator classifier, so such asubclause now reports that one message instead of the strict findings it used to accumulate. That is the same gating
the internal-port, timeout-reset-port, and array-size checks already apply.
🤖 Generated with Claude Code