From 5e5d0d3e57e661786db3e7e8390926f82a17ca5c Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Thu, 16 Feb 2017 12:40:09 +0200 Subject: [PATCH] Using separate keyword 'nonew' as reverse abstract; nonew is inherited, and nonew class can only be created from the first nonew class in the hierarchy --- src/dobject.h | 3 ++- src/sc_man_scanner.re | 1 + src/sc_man_tokens.h | 1 + src/scripting/backend/codegen.cpp | 17 +++++++++++++++-- src/scripting/vm/vmexec.h | 14 +++++++++++++- src/scripting/zscript/zcc-parse.lemon | 1 + src/scripting/zscript/zcc_compile.cpp | 6 ++++-- src/scripting/zscript/zcc_parser.cpp | 1 + src/scripting/zscript/zcc_parser.h | 3 ++- 9 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/dobject.h b/src/dobject.h index 01ee76fb2..6605cea5a 100644 --- a/src/dobject.h +++ b/src/dobject.h @@ -205,7 +205,8 @@ enum EObjectFlags OF_Transient = 1 << 11, // Object should not be archived (references to it will be nulled on disk) OF_Spawned = 1 << 12, // Thinker was spawned at all (some thinkers get deleted before spawning) OF_Released = 1 << 13, // Object was released from the GC system and should not be processed by GC function - OF_Abstract = 1 << 14, // Marks a class that cannot be created with new() function + OF_Abstract = 1 << 14, // Marks a class that cannot be created with new() function at all + OF_NoNew = 1 << 15, // Marks a class that can only be created with new() in the exact class that has this keyword }; template class TObjPtr; diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re index d4a1254d6..57a3940ef 100644 --- a/src/sc_man_scanner.re +++ b/src/sc_man_scanner.re @@ -170,6 +170,7 @@ std2: 'virtual' { RET(TK_Virtual); } 'override' { RET(TK_Override); } 'vararg' { RET(TK_VarArg); } + 'nonew' { RET(TK_NoNew); } 'super' { RET(TK_Super); } 'global' { RET(TK_Global); } 'stop' { RET(TK_Stop); } diff --git a/src/sc_man_tokens.h b/src/sc_man_tokens.h index 0227dffa9..b278de38e 100644 --- a/src/sc_man_tokens.h +++ b/src/sc_man_tokens.h @@ -112,6 +112,7 @@ xx(TK_Optional, "'optional'") xx(TK_Export, "'expert'") xx(TK_Virtual, "'virtual'") xx(TK_VarArg, "'vararg'") +xx(TK_NoNew, "'nonew'") xx(TK_Override, "'override'") xx(TK_Super, "'super'") xx(TK_Null, "'null'") diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index 1f2b038f1..fc18f481b 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -5058,13 +5058,26 @@ FxExpression *FxNew::Resolve(FCompileContext &ctx) if (val->isConstant()) { auto cls = static_cast(static_cast(val)->GetValue().GetPointer()); - if ((cls->ObjectFlags & OF_Abstract) && cls != ctx.Class) + if (cls->ObjectFlags & OF_Abstract) { - ScriptPosition.Message(MSG_ERROR, "Cannot instantiate abstract class %s outside of that class", cls->TypeName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "Cannot instantiate abstract class %s", cls->TypeName.GetChars()); delete this; return nullptr; } + if (cls->ObjectFlags & OF_NoNew) + { + PClass* pcls = cls; + while (pcls && pcls->ParentClass && (pcls->ParentClass->ObjectFlags & OF_NoNew)) + pcls = pcls->ParentClass; + if (pcls != ctx.Class) + { + ScriptPosition.Message(MSG_ERROR, "Cannot instantiate class %s directly", cls->TypeName.GetChars()); + delete this; + return nullptr; + } + } + ValueType = NewPointer(cls); } diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h index 01ad0111a..6c2eef8a1 100644 --- a/src/scripting/vm/vmexec.h +++ b/src/scripting/vm/vmexec.h @@ -788,7 +788,19 @@ begin: b = B; PClass *cls = (PClass*)(pc->op == OP_NEW ? reg.a[b] : konsta[b].v); PFunction *callingfunc = (PFunction*)konsta[C].o; // [ZZ] due to how this is set, it's always const - if ((cls->ObjectFlags & OF_Abstract) && (!callingfunc || callingfunc->OwningClass != cls)) ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s outside of that class", cls->TypeName.GetChars()); + if (cls->ObjectFlags & OF_Abstract) ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s", cls->TypeName.GetChars()); + if (cls->ObjectFlags & OF_NoNew) + { + // trace to the first nonew class in the hierarchy. + // compare that class to the context class. + // if not matching, disallow creation. + // this ensures that only the root class can have a static factory method that can also create instances of subclasses via new(). + PClass* pcls = cls; + while (pcls && pcls->ParentClass && (pcls->ParentClass->ObjectFlags & OF_NoNew)) + pcls = pcls->ParentClass; + if (!callingfunc || pcls != callingfunc->OwningClass) + ThrowAbortException(X_OTHER, "Cannot instantiate class %s directly", cls->TypeName.GetChars()); + } reg.a[a] = cls->CreateNew(); reg.atag[a] = ATAG_OBJECT; NEXTOP; diff --git a/src/scripting/zscript/zcc-parse.lemon b/src/scripting/zscript/zcc-parse.lemon index 6d3e8a856..f158bff2d 100644 --- a/src/scripting/zscript/zcc-parse.lemon +++ b/src/scripting/zscript/zcc-parse.lemon @@ -212,6 +212,7 @@ class_ancestry(X) ::= COLON dottable_id(A). { X = A; /*X-overwrites-A*/ } %type class_flags{ClassFlagsBlock} class_flags(X) ::= . { X.Flags = 0; X.Replaces = NULL; } class_flags(X) ::= class_flags(A) ABSTRACT. { X.Flags = A.Flags | ZCC_Abstract; X.Replaces = A.Replaces; } +class_flags(X) ::= class_flags(A) NONEW. { X.Flags = A.Flags | ZCC_NoNew; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; X.Replaces = A.Replaces; } class_flags(X) ::= class_flags(A) REPLACES dottable_id(B). { X.Flags = A.Flags; X.Replaces = B; } diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 187e2f341..cb93a5965 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -599,9 +599,11 @@ void ZCCCompiler::CreateClassTypes() } } if (c->Type() == nullptr) c->cls->Type = parent->FindClassTentative(c->NodeName()); - // [ZZ] if parent class is abstract, this one should be abstract as well - otherwise we can subclass Actor and be able to new() our subclass - if ((c->cls->Flags & ZCC_Abstract) || (parent && parent->ObjectFlags & OF_Abstract)) + if (c->cls->Flags & ZCC_Abstract) c->Type()->ObjectFlags |= OF_Abstract; + // [ZZ] inherit nonew keyword + if (c->cls->Flags & ZCC_NoNew || (parent && parent->ObjectFlags & OF_NoNew)) + c->Type()->ObjectFlags |= OF_NoNew; c->Type()->bExported = true; // this class is accessible to script side type casts. (The reason for this flag is that types like PInt need to be skipped.) c->cls->Symbol = new PSymbolType(c->NodeName(), c->Type()); OutNamespace->Symbols.AddSymbol(c->cls->Symbol); diff --git a/src/scripting/zscript/zcc_parser.cpp b/src/scripting/zscript/zcc_parser.cpp index 501a7b2ea..4bfb8d0f2 100644 --- a/src/scripting/zscript/zcc_parser.cpp +++ b/src/scripting/zscript/zcc_parser.cpp @@ -137,6 +137,7 @@ static void InitTokenMap() TOKENDEF (TK_Latent, ZCC_LATENT); TOKENDEF (TK_Virtual, ZCC_VIRTUAL); TOKENDEF (TK_VarArg, ZCC_VARARG); + TOKENDEF (TK_NoNew, ZCC_NONEW); TOKENDEF (TK_Override, ZCC_OVERRIDE); TOKENDEF (TK_Final, ZCC_FINAL); TOKENDEF (TK_Meta, ZCC_META); diff --git a/src/scripting/zscript/zcc_parser.h b/src/scripting/zscript/zcc_parser.h index 95bafd54f..12a5be06b 100644 --- a/src/scripting/zscript/zcc_parser.h +++ b/src/scripting/zscript/zcc_parser.h @@ -36,7 +36,8 @@ enum ZCC_Virtual = 1 << 13, ZCC_Override = 1 << 14, ZCC_Transient = 1 << 15, - ZCC_VarArg = 1 << 16 + ZCC_VarArg = 1 << 16, + ZCC_NoNew = 1 << 17, }; // Function parameter modifiers