From 2908612e84edffadbd8d2b34f3840f8a4729c332 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 12 Nov 2026 05:14:22 +0000 Subject: [PATCH] fix(codegen): declare the record argument parser the C collection runtime calls cRecSeqRuntime instantiated the sequence template over records, whose parse_seq calls sysml_parse_rec, which nothing emitted; gcc 15 makes the implicit declaration an error. CFlags now carries -Werror=implicit-function-declaration and -Werror=int-conversion so every compiler reports such an omission, and a test builds a record-sequence calc under them. Co-Authored-By: jason.han --- .../codegen-record-sequence-parser.fixed.md | 1 + internal/translate/codegen/build.go | 6 +-- internal/translate/codegen/emit_fn.go | 1 + .../codegen/record_seq_build_test.go | 39 +++++++++++++++++++ .../translate/codegen/refuse_records_test.go | 1 + 5 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 changes/unreleased/codegen-record-sequence-parser.fixed.md create mode 100644 internal/translate/codegen/record_seq_build_test.go diff --git a/changes/unreleased/codegen-record-sequence-parser.fixed.md b/changes/unreleased/codegen-record-sequence-parser.fixed.md new file mode 100644 index 0000000000..732023e62c --- /dev/null +++ b/changes/unreleased/codegen-record-sequence-parser.fixed.md @@ -0,0 +1 @@ +- The C emitter now declares `sysml_parse_rec`, the argument parser the collection runtime over records calls: a compiled calc holding a record sequence (`attribute ps : Point[0..*]`) failed to build under gcc 15, whose defaults make an implicit declaration an error. `codegen.CFlags` carries `-Werror=implicit-function-declaration -Werror=int-conversion` so every C compiler reports such an omission. diff --git a/internal/translate/codegen/build.go b/internal/translate/codegen/build.go index 603547ccaa..6ae66afe5c 100644 --- a/internal/translate/codegen/build.go +++ b/internal/translate/codegen/build.go @@ -50,9 +50,9 @@ func goCommand(name string) (string, error) { return filepath.Abs(resolved) } -// CFlags are the C compiler options; the prelude's explicit checks keep -O3 safe, -// and no contraction keeps every Real operation rounded as the interpreter rounds it. -var CFlags = []string{"-O3", "-flto", "-ffp-contract=off", "-std=gnu11", "-Wall", "-Wextra", "-Wno-unused-function", "-Wno-unused-parameter"} +// CFlags are the C compiler options; the prelude's explicit checks keep -O3 safe, and no contraction keeps +// every Real operation rounded as the interpreter rounds it. The two -Werror are gcc 15's defaults on every gcc. +var CFlags = []string{"-O3", "-flto", "-ffp-contract=off", "-std=gnu11", "-Wall", "-Wextra", "-Wno-unused-function", "-Wno-unused-parameter", "-Werror=implicit-function-declaration", "-Werror=int-conversion"} // Source renders program for target, as a complete program with a main. func Source(p *Program, target Target) ([]byte, error) { diff --git a/internal/translate/codegen/emit_fn.go b/internal/translate/codegen/emit_fn.go index 33488d072f..3ebfce0881 100644 --- a/internal/translate/codegen/emit_fn.go +++ b/internal/translate/codegen/emit_fn.go @@ -304,6 +304,7 @@ static const char *sysml_show_rec(sysml_rec *r) { sysml_format_rec(r, text, sizeof text); return text; } +static sysml_rec *sysml_parse_rec(const char *s, const char *name) { fprintf(stderr, "argument %s: %s is not a record value\n", name, s); exit(2); } ` + r.Replace(cSeqTemplate) } diff --git a/internal/translate/codegen/record_seq_build_test.go b/internal/translate/codegen/record_seq_build_test.go new file mode 100644 index 0000000000..e2cdbb118f --- /dev/null +++ b/internal/translate/codegen/record_seq_build_test.go @@ -0,0 +1,39 @@ +package codegen_test + +import ( + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/Open-MBEE/OpenSysML/internal/translate/codegen" +) + +// TestCRecordSequencesBuildUnderStrictDeclarations: a calc holding a record sequence instantiates +// the collection runtime over records, whose argument parser gcc 15 refused as an implicit declaration. +func TestCRecordSequencesBuildUnderStrictDeclarations(t *testing.T) { + cc := os.Getenv(codegen.CCompilerEnvVar) + if cc == "" { + cc = "cc" + } + if _, err := exec.LookPath(cc); err != nil { + t.Skip("no C compiler on PATH") + } + for _, flag := range []string{"-Werror=implicit-function-declaration", "-Werror=int-conversion"} { + found := false + for _, f := range codegen.CFlags { + found = found || f == flag + } + if !found { + t.Errorf("CFlags lacks %s, so a missing runtime declaration compiles on an older gcc", flag) + } + } + compiler, pkg := recordCompiler(t) + program, err := compiler.Compile(recordCalc(t, pkg, "HoldsMany"), codegen.TargetC) + if err != nil { + t.Fatal(err) + } + if err := codegen.Build(program, codegen.TargetC, filepath.Join(t.TempDir(), "records")); err != nil { + t.Fatalf("the generated C does not build under CFlags: %v", err) + } +} diff --git a/internal/translate/codegen/refuse_records_test.go b/internal/translate/codegen/refuse_records_test.go index a95b0bc7ce..b67e474536 100644 --- a/internal/translate/codegen/refuse_records_test.go +++ b/internal/translate/codegen/refuse_records_test.go @@ -52,6 +52,7 @@ const recordModel = ` calc def TakesMany { in ps : Point[0..*]; return : Real = 1.0; } calc def ReadOff { in a : Real; return : Real = Make(a).y; } calc def PassesInside { in a : Real; return : Real = Takes(new Point(a, 2.0)); } + calc def HoldsMany { in a : Real; attribute ps : Point[0..*] = (new Point(a, 1.0), new Point(2.0, a)); return : Real = ps#(2).y; } attribute def Seg { attribute a : Point; attribute b : Point; } calc def UnsetRead { in a : Real; return : Real = new Point(a).y; } calc def UnsetInSeq { in a : Real; return : Real[0..*] = (a, new Point(a).y); }