Skip to content

The C++ and .NET generators read physicalConstraints by different rules, so the next constraint added will fork the two APIs #218

Closed

Description

@matt-edmondson

What happens

The same metadata field decides a runtime guard, and the two projections test it differently.

.NET — exact value match (Semantics.SourceGenerators/Generators/QuantitiesGenerator.cs:1372-1373):

bool strictPositive = vectorForm == 0
    && overload.PhysicalConstraints?.MinExclusive == "0";

C++ — presence only (Semantics.Cpp/QuantityVocabulary.cs:155):

overload.PhysicalConstraints is null ? Magnitude.NonNegative : Magnitude.Positive));

MetadataConstraints.MinExclusive defaults to string.Empty (Semantics.Cpp/QuantityMetadata.cs:130), so a non-null constraints object with no minExclusive — or with any value other than "0" — lands on opposite answers.

Failure scenario

Add "physicalConstraints": {}, or {"minExclusive": "1"}, to any V0 overload.

C++ emits the strict guard (Semantics.Cpp/CppQuantityGenerator.cs:239-244):

string comparison = type.Magnitude == Magnitude.Positive ? ">" : ">=";
...
return $"(assert({ValueName}.count() {comparison} 0 && \"{says}\"), {ValueName})";

→ assert(value > 0), rejecting zero.

.NET keeps EnsureNonNegative, accepting zero.

The same declared quantity now has two different domains depending on which language you generate.

Why it matters

Today's three constraint sites — Wavelength, Period, HalfLife, all {"minExclusive": "0"} — agree by coincidence. So nothing is broken right now, and nothing would catch it breaking.

CLAUDE.md's design decision #4 is explicit that the strict rule is opted into by minExclusive: "0" specifically:

A V0 overload can opt into a stricter rule by declaring physicalConstraints: { "minExclusive": "0" } in dimensions.json (#51); the generator then emits Vector0Guards.EnsurePositive and rejects zero too.

The C++ side does not implement that rule — it implements "any constraints at all". The next constraint added to the metadata silently forks the two APIs, and the divergence surfaces as a C++ assertion failing on a value .NET accepts.

Suggested fix

Make QuantityVocabulary test PhysicalConstraints?.MinExclusive == "0", matching QuantitiesGenerator.

Better still, factor the predicate so both generators call one implementation — this is the second case in this repo (see the dimensional-check divergence) where one metadata file is read by two generators with no shared decision logic.

Add a Semantics.Cpp.Test case asserting the Magnitude chosen for an overload with a non-"0" constraint, so the two cannot drift again.

Activity

  1. matt-edmondson commented on Sep 11, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    Priority: Medium Effort: Small

    What needs to be done: The .NET generator tests physicalConstraints.MinExclusive == "0" exactly (the documented opt-in rule), while the C++ generator tests only whether physicalConstraints is non-null — so any future constraint that is present but isn't minExclusive: "0" (an empty object, or a different threshold) will make the two projections choose different runtime guards for the same declared quantity. No live bug today (the three existing constraint sites happen to agree by coincidence), but it's a landmine for the next metadata addition.

    Suggested next steps / acceptance criteria:

    • Make QuantityVocabulary's Magnitude selection test PhysicalConstraints?.MinExclusive == "0", matching QuantitiesGenerator's rule exactly.
    • Better (and noted as the issue's own preference): factor the predicate into one shared implementation both generators call, since this is the second instance in this repo of one metadata file being read by two generators with no shared decision logic (see Sensitivity's dimensional formula matches neither V/Pa nor A/Pa, and the .NET generator emits the operator the C++ projection refuses #216).
    • Add a Semantics.Cpp.Test case with a non-"0" physicalConstraints value (e.g. {} or minExclusive: "1"), asserting the chosen Magnitude matches what the .NET generator would choose.

    Blockers / dependencies: None identified. Worth doing in the same pass as #216's part (b) if a shared cross-generator validation module is introduced, but not blocking either way.


    Generated by Claude Code

  2. added a commit that references this issue on Oct 14, 2026
    0f8b41a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions