aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-17 15:22:23 +0000
committer_ov4 <ov4@ov4.org>2026-10-17 15:22:23 +0000
commitf7f5df01c416a10e3eb9508a7a7150c792a8b4ca (patch)
tree9d1fc624c74c129b24fe8b8a6f921f50b799e09a
parent09498bb7aab9182ba172aae1045ca895d4f422d4 (diff)
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 <noreply@anthropic.com>
-rw-r--r--lib/CodeGen/CodeGen.cc14
-rw-r--r--lib/CodeGen/FunctionGen.cc30
-rw-r--r--tests/i/multifile-extern/case.json6
-rw-r--r--tests/i/multifile-extern/expected.txt2
-rw-r--r--tests/i/multifile-extern/lib.cps6
-rw-r--r--tests/i/multifile-extern/notes.md7
-rw-r--r--tests/i/multifile-extern/program.cps6
-rw-r--r--tests/iii/multifile-extern-conflict/case.json13
-rw-r--r--tests/iii/multifile-extern-conflict/lib.cps3
-rw-r--r--tests/iii/multifile-extern-conflict/notes.md5
-rw-r--r--tests/iii/multifile-extern-conflict/program.cps4
11 files changed, 90 insertions, 6 deletions
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<SourceUnit> &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<Definition> Definitions;
std::optional<size_t> 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<SourceUnit> &Sources) {
reportError("MAIN must be a FUNCTION with no parameters returning INTEGER");
}
} else if (auto *X = dynamic_cast<ExternDeclAST*>(S.get())) {
- Reserve(X->getProto()->getName(), true);
+ Reserve(X->getProto()->getName(), true, false, true);
} else if (dynamic_cast<IncludeObjStmtAST*>(S.get())) {
} else if (auto *E = dynamic_cast<EnumTypeDeclAST*>(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<ArgumentType> 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)