aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-22 15:17:31 +0000
committer_ov4 <ov4@ov4.org>2026-10-22 15:21:11 +0000
commitfe70048b33baebdfbf7071059bd4742ec16ab408 (patch)
tree040ab1e1d6847baf8b60b8c1970543b8e578e36f
parent98ef8bc4dfcebe44eece933ff0affc776b7199dd (diff)
fix(classes): defer NEW for DECLARE without a parameterless constructor
A plain DECLARE, field or array element now calls NEW only when the class has a parameterless one; otherwise the zeroed object waits for x <- NEW C(...), as in guide section 10.2. DECLARE with parentheses still requires a matching constructor. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
-rw-r--r--AGENTS/PITFALLS/classes.md6
-rw-r--r--AGENTS/SUBSYSTEMS/CLASS_LIFETIME.md6
-rw-r--r--doc/language/keyword/cpsc-keyword-class.7.md2
-rw-r--r--lib/CodeGen/ConstructorGen.cc10
-rw-r--r--lib/CodeGen/ValueTypeGen.cc6
-rw-r--r--tests/iii/class-constructor-default-errors/case.json12
-rw-r--r--tests/iii/class-overload-errors/case.json4
7 files changed, 22 insertions, 24 deletions
diff --git a/AGENTS/PITFALLS/classes.md b/AGENTS/PITFALLS/classes.md
index 82415d7..ff61439 100644
--- a/AGENTS/PITFALLS/classes.md
+++ b/AGENTS/PITFALLS/classes.md
@@ -20,8 +20,10 @@ Part of the [pitfalls index](../PITFALLS.md).
## Construction
- PROCEDURE NEW is an ordinary case-insensitive member, also called automatically
- after zero initialization by DECLARE and assignment-only keyword NEW. Fields and
- array elements recursively require parameterless construction before the outer NEW.
+ after zero initialization by assignment-only keyword NEW and by DECLARE with
+ parentheses. A plain DECLARE (and fields and array elements, constructed before the
+ outer NEW) calls NEW only when a parameterless NEW exists (`emitDefaultConstruct`);
+ otherwise the zeroed object waits for `x <- NEW C(...)` (guide 10.2).
- Class variables and BYVAL class parameters use reference slots: keyword NEW rebinds
them, while ordinary assignment copies into their current object.
- Fields, array elements, BYREF, this and pointer dereferences receive a value copy
diff --git a/AGENTS/SUBSYSTEMS/CLASS_LIFETIME.md b/AGENTS/SUBSYSTEMS/CLASS_LIFETIME.md
index 3b9ec58..eb7f039 100644
--- a/AGENTS/SUBSYSTEMS/CLASS_LIFETIME.md
+++ b/AGENTS/SUBSYSTEMS/CLASS_LIFETIME.md
@@ -10,8 +10,10 @@ Part of the [subsystem notes](../SUBSYSTEMS.md). Builds on [classes](CLASSES.md)
marshalling and exception-aware calls.
- DECLARE zeroes its original storage on every execution; arrays loop over all flat
elements and default-construct each one.
-- Constructor argument lists are class-only, and field/array defaults require a
- parameterless NEW. Explicit NEW method calls do not reconstruct fields.
+- Constructor argument lists are class-only. Plain DECLARE, field and array defaults go
+ through `emitDefaultConstruct`, which calls NEW only when a parameterless one exists
+ (else the object stays zeroed with constructed fields; guide 10.2); DECLARE with
+ parentheses uses `emitConstruct`. Explicit NEW method calls do not reconstruct fields.
- `SymbolInfo::IsReference` marks a pointer slot for declared class variables and BYVAL
class parameters; `symbolAddress` loads their current object address for all value,
lvalue, address-of, receiver and BYREF paths. Field aliases, this and BYREF
diff --git a/doc/language/keyword/cpsc-keyword-class.7.md b/doc/language/keyword/cpsc-keyword-class.7.md
index bcb299b..ebeb98e 100644
--- a/doc/language/keyword/cpsc-keyword-class.7.md
+++ b/doc/language/keyword/cpsc-keyword-class.7.md
@@ -38,7 +38,7 @@ Classes retain record value semantics: zero initialization, whole-value copying,
A constructor is a PROCEDURE named NEW, following guide section 10.2. It may be PUBLIC or PRIVATE; FUNCTION NEW and global PROCEDURE NEW are errors. Member names are case insensitive, so New and new also name the constructor. Multiple NEW procedures are allowed when every definition specifies OVERLOAD and their parameter signatures differ. Explicit `Object.NEW(arguments)`, `CALL Object.NEW(arguments)` and `this.NEW(arguments)` behave like ordinary member calls and may be repeated; they do not zero the object or reconstruct its fields.
-`DECLARE Object : ClassName`, `DECLARE Object : ClassName()` and `DECLARE Object : ClassName(arguments)` zero the object's storage, construct its fields recursively in declaration order, then call its NEW procedure. These automatic DECLARE, field and array calls are compiler extensions. Array elements and class-valued fields in records or classes use parameterless construction, before the enclosing constructor runs. A required constructor argument cannot be omitted; supplying arguments to a class without NEW is an error. Empty parentheses are allowed for a class without NEW. Constructor arguments after ARRAY OF are unsupported. A declaration executed again, such as inside a loop, first destroys its previous live incarnation, then zeroes and constructs its original storage again.
+`DECLARE Object : ClassName` zeroes the object's storage and constructs its fields recursively in declaration order; it then calls NEW only when the class has a parameterless NEW. Otherwise the object stays unconstructed until `Object <- NEW ClassName(arguments)` binds it, as in guide section 10.2. `DECLARE Object : ClassName(arguments)` always calls the matching NEW, so `ClassName()` is an error for a class whose NEW requires arguments, and supplying arguments to a class without NEW is an error. These automatic DECLARE, field and array calls are compiler extensions. Array elements and class-valued fields in records or classes follow the plain DECLARE rule, before the enclosing constructor runs. Constructor arguments after ARRAY OF are unsupported. A declaration executed again, such as inside a loop, first destroys its previous live incarnation, then zeroes and constructs its original storage again.
An `OVERLOAD PROCEDURE NEW(BYREF Other : ClassName)` defines a copy constructor. BYVAL class lvalue arguments and RETURN of class lvalues invoke it; a same-class BYVAL constructor is rejected. Classes without one retain bitwise copying for fields without STRING ownership. Records and BYVAL arrays recursively clone STRING-bearing fields or elements. Other owning class fields retain the existing shallow-copy limitations. Class results transfer ownership directly into BYVAL arguments, RETURN and ordinary assignment without an assignment overload. Discarded results and temporary receivers live until the statement ends, including exception exits. Ordinary user BYREF calls still require bare variables; constructor calls can bind temporaries.
diff --git a/lib/CodeGen/ConstructorGen.cc b/lib/CodeGen/ConstructorGen.cc
index 8ca0258..286435e 100644
--- a/lib/CodeGen/ConstructorGen.cc
+++ b/lib/CodeGen/ConstructorGen.cc
@@ -114,8 +114,16 @@ bool CodeGen::emitConstruct(const std::string &TypeName, Value *Addr,
return true;
}
+// Guide 10.2 creation pattern: a plain DECLARE only runs NEW when the class has
+// a parameterless one; otherwise the object stays zeroed (fields constructed)
+// and a later `x <- NEW C(...)` binds it.
bool CodeGen::emitDefaultConstruct(const std::string &TypeName, Value *Addr, int Line) {
- return emitConstruct(TypeName, Addr, {}, Line);
+ const TypeInfo *Info = resolveType(TypeName);
+ if (!Info) return false;
+ for (const FuncSig *Sig : FuncGen->getOverloads(TypeName + ".NEW"))
+ if (Sig->Params.empty()) return emitConstruct(TypeName, Addr, {}, Line);
+ SaveAndRestore<int> Location(DiagnosticLine, Line);
+ return !Info->asRecord() || emitFieldDefaults(Info, Addr, Line);
}
// Default-constructs, in declaration order, each field of the record at Addr
diff --git a/lib/CodeGen/ValueTypeGen.cc b/lib/CodeGen/ValueTypeGen.cc
index e73b189..af32d74 100644
--- a/lib/CodeGen/ValueTypeGen.cc
+++ b/lib/CodeGen/ValueTypeGen.cc
@@ -262,6 +262,8 @@ void CodeGen::emitDeclareStmt(DeclareStmtAST *Stmt) {
reportError("Constructor arguments require a class type");
return;
}
- if (emitConstruct(Info->Name, Alloca, Stmt->getArgs(), Stmt->getLine()))
- activateStackObject(Lifetime);
+ bool Constructed = Stmt->hasParens()
+ ? emitConstruct(Info->Name, Alloca, Stmt->getArgs(), Stmt->getLine())
+ : emitDefaultConstruct(Info->Name, Alloca, Stmt->getLine());
+ if (Constructed) activateStackObject(Lifetime);
}
diff --git a/tests/iii/class-constructor-default-errors/case.json b/tests/iii/class-constructor-default-errors/case.json
index adcefe8..7527470 100644
--- a/tests/iii/class-constructor-default-errors/case.json
+++ b/tests/iii/class-constructor-default-errors/case.json
@@ -2,10 +2,6 @@
"errors": [
{
"reason": "Incorrect # arguments passed to Required.NEW",
- "line": 15
- },
- {
- "reason": "Incorrect # arguments passed to Required.NEW",
"line": 16
},
{
@@ -17,14 +13,6 @@
"line": 18
},
{
- "reason": "Incorrect # arguments passed to Required.NEW",
- "line": 19
- },
- {
- "reason": "Incorrect # arguments passed to Required.NEW",
- "line": 20
- },
- {
"reason": "Constructor arguments require a class type",
"line": 21
}
diff --git a/tests/iii/class-overload-errors/case.json b/tests/iii/class-overload-errors/case.json
index afc0e6a..ea103c0 100644
--- a/tests/iii/class-overload-errors/case.json
+++ b/tests/iii/class-overload-errors/case.json
@@ -5,10 +5,6 @@
"line": 4
},
{
- "reason": "No matching overload for Required.NEW\\(\\)",
- "line": 17
- },
- {
"reason": "Cannot access private method Set of type Secret",
"line": 19
},