You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Bind struct field constraints to constructor applications instead of per-field lookups #1944
This content was written by an AI agent and must be verified by a human developer. After human verification, this alert may be removed.
Summary
Struct field constraints (min:/max: on fields) are looked up per field at check and evaluation time. check_const_struct_field_constraints silently skips a field when no constraint is found, so "this field has no constraint" and "the constraint lookup missed" are indistinguishable. There is no known repro, but the gap means a missed lookup would silently skip validation.
Current state
graphcal-eval: check_const_struct_field_constraints, the field-constraint lookup in eval_expr/hir_eval.rs, and the one in execution_check/domain_resolve.rs.
Field-constraint bounds can reference evaluated constants and Nat bindings, so they are resolved in execution_check after constants are evaluated. They cannot be attached to the constructor node at check time.
Proposed direction
Build a per-DAG table of constructor applications indexed by a slot interned by the compiler (similar to CallTargets / CallSlot), rehomed through specialization. execution_check then fills resolved constraints per slot, and evaluation reads them without a name lookup. "No constraint" becomes an explicit value instead of a missing entry.
Notes
Making the skip an internal error without this redesign would add an invariant site that could fire spuriously.
This content was written by an AI agent and must be verified by a human developer. After human verification, this alert may be removed.
Found a reproducible constructor field-constraint bypass related to this issue, although it does not demonstrate the missing-lookup scenario described in the summary.
Minimal reproducer
Save as repro.gcl:
type Spec { Spec(mass: Mass(max: 2000.0 kg)) }
const node BAD: Mass = Spec(mass: 5000.0 kg).mass;
Run:
graphcal check repro.gcl
graphcal eval repro.gcl
Actual: both commands exit successfully; evaluation prints:
BAD = 5000 kg
Expected: reject the constructor application because Spec.mass exceeds its maximum of 2000.0 kg.
Controls
Changing const node BAD to node bad correctly fails at evaluation with field Spec.mass above maximum (2000 kg).
Keeping the struct as the constant's final value (const node BAD: Spec = Spec(mass: 5000.0 kg);) correctly produces a compile-time domain violation.
Cause
Constant evaluation runs with a provisional session before field constraints are resolved. eval_constructor_call in crates/graphcal-eval/src/eval_expr/hir_eval.rs skips constructor field validation when that session has no field-constraint table. Once constraints are resolved, check_const_struct_field_constraints in crates/graphcal-eval/src/execution_check/domain_resolve.rs recursively validates only the final constant values.
In the reproducer, field projection discards the temporary struct and leaves a scalar Mass value, so the later recursive check never sees the invalid constructor application. This is a validation-coverage gap rather than a missed constraint key; binding constraints to constructor applications would also need to ensure applications evaluated during the constant phase are eventually validated.
Verified locally against a fresh cargo build -p graphcal build.
Activity
shunichironomura commentedon Oct 7, 2026
Warning
This content was written by an AI agent and must be verified by a human developer. After human verification, this alert may be removed.
Found a reproducible constructor field-constraint bypass related to this issue, although it does not demonstrate the missing-lookup scenario described in the summary.
Minimal reproducer
Save as
repro.gcl:Run:
graphcal check repro.gcl graphcal eval repro.gclActual: both commands exit successfully; evaluation prints:
Expected: reject the constructor application because
Spec.massexceeds its maximum of2000.0 kg.Controls
const node BADtonode badcorrectly fails at evaluation withfield Spec.mass above maximum (2000 kg).const node BAD: Spec = Spec(mass: 5000.0 kg);) correctly produces a compile-time domain violation.Cause
Constant evaluation runs with a provisional session before field constraints are resolved.
eval_constructor_callincrates/graphcal-eval/src/eval_expr/hir_eval.rsskips constructor field validation when that session has no field-constraint table. Once constraints are resolved,check_const_struct_field_constraintsincrates/graphcal-eval/src/execution_check/domain_resolve.rsrecursively validates only the final constant values.In the reproducer, field projection discards the temporary struct and leaves a scalar
Massvalue, so the later recursive check never sees the invalid constructor application. This is a validation-coverage gap rather than a missed constraint key; binding constraints to constructor applications would also need to ensure applications evaluated during the constant phase are eventually validated.Verified locally against a fresh
cargo build -p graphcalbuild.