From f7f5df01c416a10e3eb9508a7a7150c792a8b4ca Mon Sep 17 00:00:00 2001 From: _ov4 Date: Sat, 17 Oct 2026 15:22:23 +0000 Subject: feat(codegen): allow identical EXTERN redeclarations across files Repeated EXTERN declarations with the same signature share one C symbol, as C declarations do; conflicting signatures are errors. Co-Authored-By: Claude Opus 5.5 --- lib/CodeGen/CodeGen.cc | 14 +++++++----- lib/CodeGen/FunctionGen.cc | 30 ++++++++++++++++++++++++- tests/i/multifile-extern/case.json | 6 +++++ tests/i/multifile-extern/expected.txt | 2 ++ tests/i/multifile-extern/lib.cps | 6 +++++ tests/i/multifile-extern/notes.md | 7 ++++++ tests/i/multifile-extern/program.cps | 6 +++++ tests/iii/multifile-extern-conflict/case.json | 13 +++++++++++ tests/iii/multifile-extern-conflict/lib.cps | 3 +++ tests/iii/multifile-extern-conflict/notes.md | 5 +++++ tests/iii/multifile-extern-conflict/program.cps | 4 ++++ 11 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 tests/i/multifile-extern/case.json create mode 100644 tests/i/multifile-extern/expected.txt create mode 100644 tests/i/multifile-extern/lib.cps create mode 100644 tests/i/multifile-extern/notes.md create mode 100644 tests/i/multifile-extern/program.cps create mode 100644 tests/iii/multifile-extern-conflict/case.json create mode 100644 tests/iii/multifile-extern-conflict/lib.cps create mode 100644 tests/iii/multifile-extern-conflict/notes.md create mode 100644 tests/iii/multifile-extern-conflict/program.cps diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc index 7abdad7..61fd837 100644 --- a/lib/CodeGen/CodeGen.cc +++ b/lib/CodeGen/CodeGen.cc @@ -187,14 +187,16 @@ void CodeGen::compile(const std::vector &Sources) { }); FuncGen->setVisibility([this](size_t Owner) { return isVisible(Owner); }); - struct Definition { size_t Unit; bool IsFunction; bool IsOverload; }; + struct Definition { size_t Unit; bool IsFunction; bool IsOverload; bool IsExtern; }; IdentifierMap Definitions; std::optional CodeUnit; FunctionDefAST *Entry = nullptr; size_t EntryUnit = 0; - auto Reserve = [&](const std::string &Name, bool IsFunction = false, bool IsOverload = false) { - auto [It, Added] = Definitions.emplace(Name, Definition{CurrentUnit, IsFunction, IsOverload}); - bool Compatible = IsFunction && IsOverload && It->second.IsFunction && It->second.IsOverload; + auto Reserve = [&](const std::string &Name, bool IsFunction = false, bool IsOverload = false, + bool IsExtern = false) { + auto [It, Added] = Definitions.emplace(Name, Definition{CurrentUnit, IsFunction, IsOverload, IsExtern}); + bool Compatible = (IsFunction && IsOverload && It->second.IsFunction && It->second.IsOverload) || + (IsExtern && It->second.IsExtern); if (!Added && It->second.Unit != CurrentUnit && !Compatible) reportError("Duplicate definition %s in %s and %s", Name.c_str(), Sources[It->second.Unit].Path.c_str(), Sources[CurrentUnit].Path.c_str()); @@ -216,7 +218,7 @@ void CodeGen::compile(const std::vector &Sources) { reportError("MAIN must be a FUNCTION with no parameters returning INTEGER"); } } else if (auto *X = dynamic_cast(S.get())) { - Reserve(X->getProto()->getName(), true); + Reserve(X->getProto()->getName(), true, false, true); } else if (dynamic_cast(S.get())) { } else if (auto *E = dynamic_cast(S.get())) { TypeName = E->getName(); @@ -461,6 +463,8 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name, return nullptr; } if (Overloads.size() == 1 && !Overloads.front()->IsOverload) return Overloads.front(); + // Visible EXTERN redeclarations are identical by construction. + if (Overloads.front()->IsExternal) return Overloads.front(); struct ArgumentType { const TypeInfo *Type; int Rank; }; std::vector Actual; std::string Key; diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc index 3937e01..9559717 100644 --- a/lib/CodeGen/FunctionGen.cc +++ b/lib/CodeGen/FunctionGen.cc @@ -194,6 +194,10 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) { FunctionType *FT = FunctionType::get(RetType, ArgTypes, false); auto &Overloads = Signatures[Sig.Name]; for (const FuncSig &Existing : Overloads) { + if (Existing.IsExternal) { + reportError("Function %s is already declared EXTERN", Proto->getName().c_str()); + return nullptr; + } if (!Sig.IsOverload || !Existing.IsOverload) { reportError("Function %s cannot be redefined. Multiple definition of %s; every overload must be declared OVERLOAD", Proto->getName().c_str(), Proto->getName().c_str()); @@ -304,12 +308,36 @@ bool FunctionGen::emitExternPrototype(PrototypeAST *Proto) { if (Attribute::AttrKind K = Extension(Info); K != Attribute::None) RetAttrs.addAttribute(K); } + // Repeated identical EXTERN declarations, as in C, share one symbol; each + // keeps its own unit so visibility follows the declaring file. auto &Overloads = Signatures[Sig.Name]; + if (!Overloads.empty() && Overloads.front().IsExternal) { + const FuncSig &First = Overloads.front(); + auto Same = [&](const std::string &A, const std::string &B) { + if (sameIdentifier(A, "VOID") || sameIdentifier(B, "VOID")) + return sameIdentifier(A, B); + const TypeInfo *L = Types.resolve(A); + return L && L->sameType(Types.resolve(B)); + }; + bool Matches = First.IsVariadic == Sig.IsVariadic && First.Params.size() == Sig.Params.size() && + Same(First.ReturnTypeName, Sig.ReturnTypeName); + for (size_t I = 0; Matches && I < Sig.Params.size(); ++I) + Matches = Same(First.Params[I].TypeName, Sig.Params[I].TypeName); + if (!Matches) { + reportError("Conflicting EXTERN declarations of %s", Name.c_str()); + return false; + } + Sig.ExternType = First.ExternType; + Sig.ExternAttrs = First.ExternAttrs; + PrototypeLinks.emplace(Proto, Name); + Overloads.push_back(std::move(Sig)); + return true; + } if (!Overloads.empty()) { if (Overloads.front().OwnerUnit != CurrentUnit) reportError("Duplicate definition %s in %s and %s", Name.c_str(), UnitPaths[Overloads.front().OwnerUnit].c_str(), UnitPaths[CurrentUnit].c_str()); - else reportError("Function %s cannot be redefined", Name.c_str()); + else reportError("EXTERN %s conflicts with FUNCTION/PROCEDURE %s", Name.c_str(), Name.c_str()); return false; } Sig.ExternType = FunctionType::get(RetType, ArgTypes, Proto->isVariadic()); diff --git a/tests/i/multifile-extern/case.json b/tests/i/multifile-extern/case.json new file mode 100644 index 0000000..032ec6a --- /dev/null +++ b/tests/i/multifile-extern/case.json @@ -0,0 +1,6 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ] +} diff --git a/tests/i/multifile-extern/expected.txt b/tests/i/multifile-extern/expected.txt new file mode 100644 index 0000000..19d70eb --- /dev/null +++ b/tests/i/multifile-extern/expected.txt @@ -0,0 +1,2 @@ +3 +8 diff --git a/tests/i/multifile-extern/lib.cps b/tests/i/multifile-extern/lib.cps new file mode 100644 index 0000000..52ccf1f --- /dev/null +++ b/tests/i/multifile-extern/lib.cps @@ -0,0 +1,6 @@ +INCLUDE_OBJ "__libc" +TYPE NativeInt = C_TYPE("int") +EXTERN "C" EXTERN_FUNCTION abs(NativeInt) RETURNS C_INT +FUNCTION Twice(N : C_INT) RETURNS C_INT + RETURN abs(N) + abs(N) +ENDFUNCTION diff --git a/tests/i/multifile-extern/notes.md b/tests/i/multifile-extern/notes.md new file mode 100644 index 0000000..f326c82 --- /dev/null +++ b/tests/i/multifile-extern/notes.md @@ -0,0 +1,7 @@ +# multifile-extern + +Human review: pending. Expected output was independently traced by AI, not captured from cpsc. + +Basis: compiler extension; identical EXTERN declarations may repeat across files, as C declarations may. + +Trace: abs(-3)=3. Twice(-4) returns abs(-4)+abs(-4)=8. diff --git a/tests/i/multifile-extern/program.cps b/tests/i/multifile-extern/program.cps new file mode 100644 index 0000000..7a0cd2d --- /dev/null +++ b/tests/i/multifile-extern/program.cps @@ -0,0 +1,6 @@ +// Both files declare the same C function; aliases of int are interchangeable. +INCLUDE "lib.cps" +INCLUDE_OBJ "__libc" +EXTERN "C" EXTERN_FUNCTION abs(C_INT) RETURNS C_INT +OUTPUT abs((C_INT)-3) +OUTPUT Twice((C_INT)-4) diff --git a/tests/iii/multifile-extern-conflict/case.json b/tests/iii/multifile-extern-conflict/case.json new file mode 100644 index 0000000..1eb3f1d --- /dev/null +++ b/tests/iii/multifile-extern-conflict/case.json @@ -0,0 +1,13 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ], + "errors": [ + { + "reason": "Conflicting EXTERN declarations of abs", + "line": 3, + "file": "lib.cps" + } + ] +} diff --git a/tests/iii/multifile-extern-conflict/lib.cps b/tests/iii/multifile-extern-conflict/lib.cps new file mode 100644 index 0000000..2c15253 --- /dev/null +++ b/tests/iii/multifile-extern-conflict/lib.cps @@ -0,0 +1,3 @@ +// The result type differs from program.cps. +INCLUDE_OBJ "__libc" +EXTERN "C" EXTERN_FUNCTION abs(C_INT) RETURNS C_DOUBLE diff --git a/tests/iii/multifile-extern-conflict/notes.md b/tests/iii/multifile-extern-conflict/notes.md new file mode 100644 index 0000000..6ac25b7 --- /dev/null +++ b/tests/iii/multifile-extern-conflict/notes.md @@ -0,0 +1,5 @@ +# multifile-extern-conflict + +Compiler extension: repeated EXTERN declarations must have identical signatures; lib.cps changes the result type. + +Require exit 1 and the cause on the second declaration. diff --git a/tests/iii/multifile-extern-conflict/program.cps b/tests/iii/multifile-extern-conflict/program.cps new file mode 100644 index 0000000..058cd5e --- /dev/null +++ b/tests/iii/multifile-extern-conflict/program.cps @@ -0,0 +1,4 @@ +INCLUDE "lib.cps" +INCLUDE_OBJ "__libc" +EXTERN "C" EXTERN_FUNCTION abs(C_INT) RETURNS C_INT +OUTPUT abs((C_INT)-3) -- cgit v1.3.1-4-g156e