aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/cps/CodeGen.h11
-rw-r--r--include/cps/TypeSystem.h2
-rw-r--r--lib/CodeGen/CodeGen.cc18
-rw-r--r--lib/CodeGen/Designator.cc56
-rw-r--r--lib/CodeGen/TemplateGen.cc161
-rw-r--r--lib/CodeGen/TypeSystem.cc5
-rw-r--r--tests/i/template-classes/case.json1
-rw-r--r--tests/i/template-classes/expected.txt15
-rw-r--r--tests/i/template-classes/notes.md7
-rw-r--r--tests/i/template-classes/program.cps110
-rw-r--r--tests/iii/template-class-errors/case.json8
-rw-r--r--tests/iii/template-class-errors/program.cps13
-rw-r--r--tests/iii/template-class-pack-field/case.json5
-rw-r--r--tests/iii/template-class-pack-field/program.cps5
14 files changed, 381 insertions, 36 deletions
diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h
index 23487f6..4730585 100644
--- a/include/cps/CodeGen.h
+++ b/include/cps/CodeGen.h
@@ -26,6 +26,7 @@ class ExceptionHandler;
class CastExprAST;
class NewExprAST;
class TemplateDeclAST;
+class ClassTypeDeclAST;
class CodeGen {
std::unique_ptr<llvm::LLVMContext> TheContext;
@@ -43,6 +44,7 @@ class CodeGen {
TemplateDeclAST *Decl;
std::string Arguments;
std::unique_ptr<FunctionDefAST> Function;
+ std::unique_ptr<ClassTypeDeclAST> Class;
const FuncSig *Signature;
size_t OwnerUnit;
size_t RequesterUnit;
@@ -52,6 +54,10 @@ class CodeGen {
std::map<const TemplateDeclAST *, IdentifierMap<SymbolInfo>> TemplateSymbols;
size_t NextTemplateBody = 0;
unsigned TemplateDepth = 0;
+ bool TypePrePass = false;
+ bool SpeculativeTemplateResolution = false;
+ IdentifierSet FailedTemplateTypes;
+ std::vector<size_t> DeferredClassPrototypes;
bool hasCallable(const std::string &Name) const;
bool hasFunctionTemplate(const std::string &Name) const;
void validateTemplates();
@@ -59,6 +65,10 @@ class CodeGen {
const FuncSig *instantiateFunction(const TemplateDefinition &Definition,
const IdentifierMap<std::vector<std::string>> &Bindings);
void drainTemplateBodies();
+ const TypeInfo *instantiateClass(const std::string &Name);
+ std::optional<std::string> canonicalTemplateArgument(const std::string &Name);
+ void emitClassPrototypes(ClassTypeDeclAST *Class);
+ void emitDeferredClassPrototypes();
std::vector<IdentifierMap<SymbolInfo>> Exports;
bool isVisible(size_t Owner) const;
void selectUnit(size_t Unit);
@@ -170,6 +180,7 @@ class CodeGen {
// TYPE-system registration (Designator.cc) and operators (TypeCheckGen.cc).
void runTypePrePass(const std::vector<std::unique_ptr<StmtAST>> &Statements, int Phase);
+ void registerClassMethods(ClassTypeDeclAST *Class);
llvm::Value *emitUserKindBinaryOp(int Op, llvm::Value *L, const TypeInfo *LT,
llvm::Value *R, const TypeInfo *RT, int Line);
diff --git a/include/cps/TypeSystem.h b/include/cps/TypeSystem.h
index e4663eb..2d885fa 100644
--- a/include/cps/TypeSystem.h
+++ b/include/cps/TypeSystem.h
@@ -122,6 +122,7 @@ class TypeSystem {
IdentifierMap<EnumConstant> EnumConstants;
std::function<bool(const std::string &)> Visible;
+ std::function<const TypeInfo *(const std::string &)> ResolveMiss;
void registerBuiltins();
const TypeInfo *registerType(const std::string &TypeName,
llvm::Type *LLVMType,
@@ -131,6 +132,7 @@ public:
explicit TypeSystem(llvm::LLVMContext &Ctx);
void setVisibility(std::function<bool(const std::string &)> Predicate) { Visible = std::move(Predicate); }
+ void setResolveMiss(std::function<const TypeInfo *(const std::string &)> Hook) { ResolveMiss = std::move(Hook); }
const TypeInfo *resolve(const std::string &TypeName) const;
// Two-pass user-type registration (pass A then pass B, both in source
diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc
index 9a678ca..fb86120 100644
--- a/lib/CodeGen/CodeGen.cc
+++ b/lib/CodeGen/CodeGen.cc
@@ -182,9 +182,11 @@ void CodeGen::compile(const std::vector<SourceUnit> &Sources) {
if (Sources.empty()) { reportError("no main program"); return; }
Exports.resize(Sources.size());
Types->setVisibility([this](const std::string &Name) {
+ if (FailedTemplateTypes.count(Name)) return false;
auto It = TypeOwners.find(Name);
return It == TypeOwners.end() || isVisible(It->second);
});
+ Types->setResolveMiss([this](const std::string &Name) { return instantiateClass(Name); });
FuncGen->setVisibility([this](size_t Owner) { return isVisible(Owner); });
struct Definition { size_t Unit; bool IsFunction; bool IsOverload; bool IsExtern; bool IsTemplate; int Line; };
@@ -277,6 +279,7 @@ void CodeGen::compile(const std::vector<SourceUnit> &Sources) {
BasicBlock *UnhandledBB = BasicBlock::Create(*TheContext, "unhandled_exception", F);
Exceptions->pushHandler(UnhandledBB);
+ TypePrePass = true;
// Reserve names across every unit before resolving any type payload.
for (size_t I = 0; I < Sources.size(); ++I) {
selectUnit(I);
@@ -294,6 +297,8 @@ void CodeGen::compile(const std::vector<SourceUnit> &Sources) {
runTypePrePass(Sources[I].Statements, 2);
};
for (size_t I = 0; I < Sources.size(); ++I) DefineTypes(I);
+ TypePrePass = false;
+ emitDeferredClassPrototypes();
for (size_t I = 0; I < Sources.size(); ++I) {
selectUnit(I);
@@ -304,11 +309,7 @@ void CodeGen::compile(const std::vector<SourceUnit> &Sources) {
if (auto *Extern = dynamic_cast<ExternDeclAST*>(Stmt.get()))
FuncGen->emitExternPrototype(Extern->getProto());
if (auto *Class = dynamic_cast<ClassTypeDeclAST*>(Stmt.get())) {
- for (const auto &Method : Class->getMethods()) {
- SaveAndRestore<int> MethodLocation(DiagnosticLine, Method->getLine());
- if (isRegisteredMethod(Class->getName(), Method->getProto()))
- FuncGen->emitPrototype(Method->getProto());
- }
+ emitClassPrototypes(Class);
}
}
}
@@ -573,14 +574,15 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name,
if (HasPackParam ? Actual.size() < FixedCount : Actual.size() != FixedCount) continue;
detail::TemplateBindings Bindings;
for (const auto &Param : TemplateParams) Bindings.emplace(Param.Name, std::vector<std::string>{});
+ SaveAndRestore<bool> Speculation(SpeculativeTemplateResolution, true);
bool Match = true;
if (HasExplicit) {
size_t FixedTypes = TemplateParams.size() - Variadic;
if (!Variadic && Explicit->size() > FixedTypes) continue;
for (size_t I = 0; I < Explicit->size(); ++I) {
- const TypeInfo *Type = resolveType((*Explicit)[I]);
- if (!Type || Type->isVoid()) { Match = false; break; }
- Bindings.at(TemplateParams[std::min(I, FixedTypes)].Name).push_back(Type->Name);
+ auto Type = canonicalTemplateArgument((*Explicit)[I]);
+ if (!Type) { Match = false; break; }
+ Bindings.at(TemplateParams[std::min(I, FixedTypes)].Name).push_back(*Type);
}
}
if (!Match) continue;
diff --git a/lib/CodeGen/Designator.cc b/lib/CodeGen/Designator.cc
index 12d3b60..ed43cbe 100644
--- a/lib/CodeGen/Designator.cc
+++ b/lib/CodeGen/Designator.cc
@@ -42,35 +42,45 @@ void CodeGen::runTypePrePass(const std::vector<std::unique_ptr<StmtAST>> &Statem
if (!Ok) reportError("%s", Err.c_str());
}
- if (Phase == 2) for (const auto &S : Statements) {
- auto *Class = dynamic_cast<ClassTypeDeclAST*>(S.get());
- if (!Class) continue;
- auto &Methods = ClassMethods[Class->getName()];
- IdentifierSet Names;
- for (const auto &Field : Class->getFields()) {
- Names.insert(Field.Name);
- if (sameIdentifier(Field.Name, "this")) {
- SaveAndRestore<int> Location(DiagnosticLine, Class->getLine());
- reportError("A class field cannot be named this");
- }
+ if (Phase == 2) for (const auto &S : Statements)
+ if (auto *Class = dynamic_cast<ClassTypeDeclAST*>(S.get())) registerClassMethods(Class);
+}
+
+void CodeGen::registerClassMethods(ClassTypeDeclAST *Class) {
+ auto &Methods = ClassMethods[Class->getName()];
+ IdentifierSet Names;
+ for (const auto &Field : Class->getFields()) {
+ Names.insert(Field.Name);
+ if (sameIdentifier(Field.Name, "this")) {
+ SaveAndRestore<int> Location(DiagnosticLine, Class->getLine());
+ reportError("A class field cannot be named this");
}
- for (const auto &Method : Class->getMethods()) {
- SaveAndRestore<int> Location(DiagnosticLine, Method->getLine());
- const std::string &Name = Method->getProto()->getName();
- if (sameIdentifier(Name, "this")) {
- reportError("A method cannot be named this");
- } else if (Names.count(Name) ||
- (!Methods[Name].empty() && (!Method->getProto()->isOverload() ||
- !Methods[Name].front()->getProto()->isOverload()))) {
- reportError("Duplicate member %s in class %s", Name.c_str(), Class->getName().c_str());
- } else {
- Methods[Name].push_back(Method.get());
- }
+ }
+ for (const auto &Method : Class->getMethods()) {
+ SaveAndRestore<int> Location(DiagnosticLine, Method->getLine());
+ const std::string &Name = Method->getProto()->getName();
+ if (sameIdentifier(Name, "this")) {
+ reportError("A method cannot be named this");
+ } else if (Names.count(Name) ||
+ (!Methods[Name].empty() && (!Method->getProto()->isOverload() ||
+ !Methods[Name].front()->getProto()->isOverload()))) {
+ reportError("Duplicate member %s in class %s", Name.c_str(), Class->getName().c_str());
+ } else {
+ Methods[Name].push_back(Method.get());
}
}
}
+void CodeGen::emitClassPrototypes(ClassTypeDeclAST *Class) {
+ for (const auto &Method : Class->getMethods()) {
+ SaveAndRestore<int> Location(DiagnosticLine, Method->getLine());
+ if (isRegisteredMethod(Class->getName(), Method->getProto()))
+ FuncGen->emitPrototype(Method->getProto());
+ }
+}
+
const std::vector<FunctionDefAST *> *CodeGen::findMethod(const std::string &Class, const std::string &Name) const {
+ if (Class.find('<') != std::string::npos && !resolveType(Class)) return nullptr;
auto C = ClassMethods.find(Class);
if (C == ClassMethods.end()) return nullptr;
auto M = C->second.find(Name);
diff --git a/lib/CodeGen/TemplateGen.cc b/lib/CodeGen/TemplateGen.cc
index a3fd4ec..a094c17 100644
--- a/lib/CodeGen/TemplateGen.cc
+++ b/lib/CodeGen/TemplateGen.cc
@@ -7,6 +7,161 @@ using namespace cps;
using namespace cps::detail;
using namespace llvm;
+namespace {
+// Source template lists are nonempty; substitution may produce Empty<>.
+bool splitTemplateType(const std::string &Name, std::string &Base,
+ std::vector<std::string> &Arguments) {
+ size_t Open = Name.find('<');
+ Base = Name.substr(0, Open);
+ if (Open == std::string::npos) return true;
+ if (Name.empty() || Name.back() != '>') return false;
+ size_t Start = Open + 1;
+ unsigned Depth = 0;
+ for (size_t I = Start; I + 1 < Name.size(); ++I) {
+ if (Name[I] == '<') ++Depth;
+ if (Name[I] == '>') {
+ if (!Depth) return false;
+ --Depth;
+ }
+ if (!Depth && Name[I] == ',') {
+ if (I == Start) return false;
+ Arguments.push_back(Name.substr(Start, I - Start));
+ Start = I + 1;
+ }
+ }
+ if (Depth) return false;
+ if (Start + 1 < Name.size()) Arguments.push_back(Name.substr(Start, Name.size() - Start - 1));
+ else if (Start != Open + 1) return false;
+ return true;
+}
+}
+
+std::optional<std::string> CodeGen::canonicalTemplateArgument(const std::string &Name) {
+ SaveAndRestore<bool> Speculation(SpeculativeTemplateResolution, true);
+ if (const TypeInfo *Type = resolveType(Name))
+ return Type->isVoid() ? std::nullopt : std::optional<std::string>(Type->Name);
+ if (FailedTemplateTypes.count(Name)) return std::nullopt;
+ std::string Base;
+ std::vector<std::string> Arguments;
+ if (!splitTemplateType(Name, Base, Arguments) || Name.find('<') == std::string::npos)
+ return std::nullopt;
+ auto Definitions = Templates.find(Base);
+ if (Definitions == Templates.end()) return std::nullopt;
+ for (const auto &Definition : Definitions->second) {
+ if (!Definition.Decl->getClass() || !isVisible(Definition.OwnerUnit)) continue;
+ size_t Fixed = Definition.Decl->getParams().size() - Definition.Decl->isVariadic();
+ if (Arguments.size() < Fixed || (!Definition.Decl->isVariadic() && Arguments.size() != Fixed))
+ return std::nullopt;
+ std::string Canonical = Definition.Decl->getName() + "<";
+ for (size_t I = 0; I < Arguments.size(); ++I) {
+ auto Argument = canonicalTemplateArgument(Arguments[I]);
+ if (!Argument) return std::nullopt;
+ if (I) Canonical += ',';
+ Canonical += *Argument;
+ }
+ return Canonical + ">";
+ }
+ return std::nullopt;
+}
+
+const TypeInfo *CodeGen::instantiateClass(const std::string &Name) {
+ std::string Base;
+ std::vector<std::string> Arguments;
+ bool Valid = splitTemplateType(Name, Base, Arguments);
+ auto Definitions = Templates.find(Base);
+ if (Definitions == Templates.end()) return nullptr;
+ const TemplateDefinition *Definition = nullptr;
+ for (const auto &Candidate : Definitions->second)
+ if (Candidate.Decl->getClass() && isVisible(Candidate.OwnerUnit)) {
+ Definition = &Candidate;
+ break;
+ }
+ if (!Definition || FailedTemplateTypes.count(Name)) return nullptr;
+ auto Fail = [&](const std::string &Message) -> const TypeInfo * {
+ if (!SpeculativeTemplateResolution && FailedTemplateTypes.insert(Name).second)
+ reportError("%s", Message.c_str());
+ return nullptr;
+ };
+ if (Name.find('<') == std::string::npos)
+ return Fail("Class template " + Base + " requires template arguments");
+ if (!Valid) return Fail("Invalid class template type " + Name);
+ const auto &Params = Definition->Decl->getParams();
+ size_t Fixed = Params.size() - Definition->Decl->isVariadic();
+ if (Arguments.size() < Fixed || (!Definition->Decl->isVariadic() && Arguments.size() != Fixed))
+ return Fail("Wrong number of template arguments for " + Base);
+ TemplateBindings Bindings;
+ for (const auto &Param : Params) Bindings.emplace(Param.Name, std::vector<std::string>{});
+ std::string Key;
+ for (size_t I = 0; I < Arguments.size(); ++I) {
+ const TypeInfo *Argument = resolveType(Arguments[I]);
+ if (!Argument || Argument->isVoid()) return Fail("Unknown or invalid template argument " + Arguments[I] + " for " + Base);
+ if (!Key.empty()) Key += ',';
+ Key += Argument->Name;
+ Bindings.at(Params[std::min(I, Fixed)].Name).push_back(Argument->Name);
+ }
+ std::string InstanceName = Definition->Decl->getName() + "<" + Key + ">";
+ if (FailedTemplateTypes.count(InstanceName)) return nullptr;
+ for (const auto &Instance : TemplateInstances)
+ if (Instance.Class && Instance.Decl == Definition->Decl && sameIdentifier(Instance.Arguments, Key))
+ return Types->resolve(InstanceName);
+ // Candidate matching must neither emit diagnostics nor reserve failed types.
+ // Its actual class type is already available, so an exact match reuses it above.
+ if (SpeculativeTemplateResolution) return nullptr;
+ if (TemplateDepth >= 64) return Fail("Template instantiation depth exceeds 64");
+
+ size_t Requester = VisibilityUnit.value_or(CurrentUnit);
+ size_t SavedUnit = CurrentUnit;
+ scope_exit RestoreUnit([&] { selectUnit(SavedUnit); });
+ SaveAndRestore<std::optional<size_t>> Visibility(VisibilityUnit, Requester);
+ SaveAndRestore<unsigned> Depth(TemplateDepth, TemplateDepth + 1);
+ selectUnit(Definition->OwnerUnit);
+ SaveAndRestore<int> Location(DiagnosticLine, Definition->Decl->getLine());
+ TemplateClone Clone(Bindings, [this](int Line, const std::string &Message) {
+ SaveAndRestore<int> Location(DiagnosticLine, Line ? Line : DiagnosticLine);
+ reportError("%s", Message.c_str());
+ });
+ auto Class = Clone.klass(Definition->Decl->getClass(), InstanceName);
+ if (!Class) {
+ FailedTemplateTypes.insert(Name);
+ return nullptr;
+ }
+ std::string Error;
+ if (!Types->declareUserType(InstanceName, TypeKind::Record, Error, true)) return Fail(Error);
+ TypeOwners[InstanceName] = Definition->OwnerUnit;
+ // Reserve and cache before fields resolve: recursion observes an opaque record
+ // and defineRecord diagnoses recursive by-value containment.
+ auto *ClonedClass = Class.get();
+ size_t Index = TemplateInstances.size();
+ TemplateInstances.push_back({Definition->Decl, Key, nullptr, std::move(Class), nullptr,
+ Definition->OwnerUnit, Requester, TemplateDepth});
+ if (!Types->defineRecord(InstanceName, ClonedClass->getFields(), Error)) {
+ Fail(Error);
+ FailedTemplateTypes.insert(InstanceName);
+ return nullptr;
+ }
+ registerClassMethods(ClonedClass);
+ if (TypePrePass) DeferredClassPrototypes.push_back(Index);
+ else emitClassPrototypes(ClonedClass);
+ return Types->resolve(InstanceName);
+}
+
+void CodeGen::emitDeferredClassPrototypes() {
+ size_t SavedUnit = CurrentUnit;
+ scope_exit RestoreUnit([&] { selectUnit(SavedUnit); });
+ for (size_t Index : DeferredClassPrototypes) {
+ const auto &Instance = TemplateInstances[Index];
+ auto *Class = Instance.Class.get();
+ size_t Requester = Instance.RequesterUnit;
+ size_t Owner = Instance.OwnerUnit;
+ unsigned Depth = Instance.Depth;
+ SaveAndRestore<std::optional<size_t>> Visibility(VisibilityUnit, Requester);
+ SaveAndRestore<unsigned> InstantiationDepth(TemplateDepth, Depth);
+ selectUnit(Owner);
+ emitClassPrototypes(Class);
+ }
+ DeferredClassPrototypes.clear();
+}
+
bool CodeGen::hasFunctionTemplate(const std::string &Name) const {
auto It = Templates.find(Name);
if (It == Templates.end()) return false;
@@ -94,7 +249,7 @@ const FuncSig *CodeGen::instantiateFunction(const TemplateDefinition &Definition
const FuncSig *Signature = nullptr;
for (const auto *Sig : Signatures)
if (Sig->Prototype == Function->getProto()) Signature = Sig;
- TemplateInstances.push_back({Definition.Decl, Key, std::move(Function), Signature,
+ TemplateInstances.push_back({Definition.Decl, Key, std::move(Function), nullptr, Signature,
Definition.OwnerUnit, Requester, TemplateDepth + 1});
return Signature;
}
@@ -106,6 +261,7 @@ void CodeGen::drainTemplateBodies() {
// Emission may append instances, so keep no vector element references.
const auto &Instance = TemplateInstances[NextTemplateBody++];
auto *Function = Instance.Function.get();
+ auto *Class = Instance.Class.get();
auto *Declaration = Instance.Decl;
size_t Owner = Instance.OwnerUnit;
size_t Requester = Instance.RequesterUnit;
@@ -114,6 +270,7 @@ void CodeGen::drainTemplateBodies() {
SaveAndRestore<unsigned> InstantiationDepth(TemplateDepth, Depth);
SaveAndRestore<IdentifierMap<SymbolInfo>> SymbolContext(Symbols, TemplateSymbols.at(Declaration));
selectUnit(Owner);
- emitStmt(Function);
+ if (Class && !FailedTemplateTypes.count(Class->getName())) emitStmt(Class);
+ if (Function) emitStmt(Function);
}
}
diff --git a/lib/CodeGen/TypeSystem.cc b/lib/CodeGen/TypeSystem.cc
index 6cf0f00..c5db1eb 100644
--- a/lib/CodeGen/TypeSystem.cc
+++ b/lib/CodeGen/TypeSystem.cc
@@ -111,9 +111,8 @@ bool TypeSystem::sameType(const std::string &Left, const std::string &Right) con
const TypeInfo *TypeSystem::resolve(const std::string &TypeName) const {
auto It = Types.find(TypeName);
- if (It == Types.end() || (Visible && !Visible(TypeName))) {
- return nullptr;
- }
+ if (It == Types.end()) return ResolveMiss ? ResolveMiss(TypeName) : nullptr;
+ if (Visible && !Visible(TypeName)) return nullptr;
return &It->second;
}
diff --git a/tests/i/template-classes/case.json b/tests/i/template-classes/case.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/tests/i/template-classes/case.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/i/template-classes/expected.txt b/tests/i/template-classes/expected.txt
new file mode 100644
index 0000000..9413c45
--- /dev/null
+++ b/tests/i/template-classes/expected.txt
@@ -0,0 +1,15 @@
+FALSE
+0
+7
+3
+2.500000
+30
+20
+10
+5
+9
+alpha
+beta
+1
+2.500000
+c
diff --git a/tests/i/template-classes/notes.md b/tests/i/template-classes/notes.md
new file mode 100644
index 0000000..96cfe22
--- /dev/null
+++ b/tests/i/template-classes/notes.md
@@ -0,0 +1,7 @@
+# Class templates
+
+Human review: pending.
+
+TEMPLATE is a compiler extension. Holder forces Box<INTEGER> to instantiate during the type pre-pass. Make<Box<BOOLEAN>>() first creates Box<BOOLEAN> through an explicit function argument and returns its default FALSE value. Holder's integer field defaults to zero. Box's explicit constructor then stores 7. Pair preserves its INTEGER and REAL fields, giving 3 and 2.500000. Stack3 pushes 10, 20 and 30 into three fixed scalar slots and pops them in reverse order.
+
+Keyword NEW constructs a fresh Box containing 5; DELETE releases that heap object. The nested Box copies the inner integer value 9. Box<STRING> assignment copies its owning STRING field independently: changing Text to beta leaves Copy equal to alpha, and both values remain valid through cleanup. Group expands three method parameters and forwards them to PrintAll, yielding 1, 2.500000 and c. ShowGroup() instantiates an internally empty Group<> and its parameterless Show, producing no output.
diff --git a/tests/i/template-classes/program.cps b/tests/i/template-classes/program.cps
new file mode 100644
index 0000000..90427d3
--- /dev/null
+++ b/tests/i/template-classes/program.cps
@@ -0,0 +1,110 @@
+TEMPLATE <T>
+CLASS Box
+ PRIVATE Value : T
+ PUBLIC OVERLOAD PROCEDURE NEW()
+ ENDPROCEDURE
+ PUBLIC OVERLOAD PROCEDURE NEW(V : T)
+ Value <- V
+ ENDPROCEDURE
+ PUBLIC FUNCTION Get() RETURNS T
+ RETURN Value
+ ENDFUNCTION
+ PUBLIC PROCEDURE Set(V : T)
+ Value <- V
+ ENDPROCEDURE
+ENDCLASS
+
+TEMPLATE <T>
+FUNCTION Make() RETURNS T
+ DECLARE Value : T
+ RETURN Value
+ENDFUNCTION
+
+TEMPLATE <A, B>
+CLASS Pair
+ First : A
+ Second : B
+ PROCEDURE NEW(Left : A, Right : B)
+ First <- Left
+ Second <- Right
+ ENDPROCEDURE
+ENDCLASS
+
+TEMPLATE <T>
+CLASS Stack3
+ PRIVATE First : T
+ PRIVATE Second : T
+ PRIVATE Third : T
+ PRIVATE Count : INTEGER
+ PROCEDURE Push(Value : T)
+ Count <- Count + 1
+ CASE OF Count
+ 1 : First <- Value
+ 2 : Second <- Value
+ 3 : Third <- Value
+ ENDCASE
+ ENDPROCEDURE
+ FUNCTION Pop() RETURNS T
+ Count <- Count - 1
+ CASE OF Count
+ 2 : RETURN Third
+ 1 : RETURN Second
+ OTHERWISE : RETURN First
+ ENDCASE
+ ENDFUNCTION
+ENDCLASS
+
+OVERLOAD PROCEDURE PrintAll()
+ENDPROCEDURE
+TEMPLATE <T, Rest...>
+OVERLOAD PROCEDURE PrintAll(First : T, Others : Rest...)
+ OUTPUT First
+ CALL PrintAll(Others...)
+ENDPROCEDURE
+
+TEMPLATE <Types...>
+CLASS Group
+ PROCEDURE Show(Values : Types...)
+ CALL PrintAll(Values...)
+ ENDPROCEDURE
+ENDCLASS
+TEMPLATE <Types...>
+PROCEDURE ShowGroup(Values : Types...)
+ DECLARE Items : Group<Types...>
+ CALL Items.Show(Values...)
+ENDPROCEDURE
+
+// Instantiation during the type pre-pass defers the Box method prototypes.
+TYPE Holder
+ DECLARE Item : Box<INTEGER>
+ENDTYPE
+OUTPUT Make<Box<BOOLEAN>>().Get()
+DECLARE H : Holder
+OUTPUT H.Item.Get()
+DECLARE B : Box<INTEGER>(7)
+OUTPUT B.Get()
+DECLARE P : Pair<INTEGER, REAL>(3, 2.5)
+OUTPUT P.First
+OUTPUT P.Second
+DECLARE S : Stack3<INTEGER>
+CALL S.Push(10)
+CALL S.Push(20)
+CALL S.Push(30)
+OUTPUT S.Pop()
+OUTPUT S.Pop()
+OUTPUT S.Pop()
+B <- NEW Box<INTEGER>(5)
+OUTPUT B.Get()
+DELETE B
+DECLARE Inner : Box<INTEGER>(9)
+DECLARE Outer : Box<Box<INTEGER>>(Inner)
+OUTPUT Outer.Get().Get()
+DECLARE Text : Box<STRING>("alpha")
+DECLARE Copy : Box<STRING>
+Copy <- Text
+CALL Text.Set("beta")
+OUTPUT Copy.Get()
+OUTPUT Text.Get()
+DECLARE Values : Group<INTEGER, REAL, CHAR>
+CALL Values.Show(1, 2.5, 'c')
+CALL ShowGroup()
diff --git a/tests/iii/template-class-errors/case.json b/tests/iii/template-class-errors/case.json
new file mode 100644
index 0000000..100438f
--- /dev/null
+++ b/tests/iii/template-class-errors/case.json
@@ -0,0 +1,8 @@
+{
+ "errors": [
+ {"reason": "No matching overload for Make", "line": 10},
+ {"reason": "Wrong number of template arguments for Box", "line": 11},
+ {"reason": "Class template Box requires template arguments", "line": 12},
+ {"reason": "Unknown or invalid template argument Nope for Box", "line": 13}
+ ]
+}
diff --git a/tests/iii/template-class-errors/program.cps b/tests/iii/template-class-errors/program.cps
new file mode 100644
index 0000000..67f8db4
--- /dev/null
+++ b/tests/iii/template-class-errors/program.cps
@@ -0,0 +1,13 @@
+TEMPLATE <T>
+CLASS Box
+ Value : T
+ENDCLASS
+TEMPLATE <T>
+FUNCTION Make() RETURNS T
+ DECLARE Value : T
+ RETURN Value
+ENDFUNCTION
+OUTPUT Make<Box<Nope>>()
+DECLARE TooMany : Box<INTEGER, REAL>
+DECLARE TooFew : Box
+DECLARE Unknown : Box<Nope>
diff --git a/tests/iii/template-class-pack-field/case.json b/tests/iii/template-class-pack-field/case.json
new file mode 100644
index 0000000..2ad0199
--- /dev/null
+++ b/tests/iii/template-class-pack-field/case.json
@@ -0,0 +1,5 @@
+{
+ "errors": [
+ {"reason": "pack.*field|field.*pack", "line": 3}
+ ]
+}
diff --git a/tests/iii/template-class-pack-field/program.cps b/tests/iii/template-class-pack-field/program.cps
new file mode 100644
index 0000000..83f30bb
--- /dev/null
+++ b/tests/iii/template-class-pack-field/program.cps
@@ -0,0 +1,5 @@
+TEMPLATE <Rest...>
+CLASS Invalid
+ Values : Rest...
+ENDCLASS
+OUTPUT 0