diff options
author | Juan M Gómez <info@jmgomez.me> | 2023-09-14 16:37:30 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-14 17:37:30 +0200 |
commit | 96e1949610bc805af451903ba5cc4d483f7b4dce (patch) | |
tree | b99c6f5691beefdf76deb8f72f4302935ce922e9 | |
parent | ac1804aba665b34a01cb014183f8fff0ba6db738 (diff) | |
download | Nim-96e1949610bc805af451903ba5cc4d483f7b4dce.tar.gz |
implements RFC: [C++] Constructors as default initializers (#22694)
Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
-rw-r--r-- | compiler/ccgstmts.nim | 15 | ||||
-rw-r--r-- | compiler/ccgtypes.nim | 18 | ||||
-rw-r--r-- | compiler/cgen.nim | 12 | ||||
-rw-r--r-- | compiler/modulegraphs.nim | 3 | ||||
-rw-r--r-- | compiler/semstmts.nim | 79 | ||||
-rw-r--r-- | tests/cpp/tinitializers.nim | 33 |
6 files changed, 119 insertions, 41 deletions
diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index d6220afbf..de358e242 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -289,14 +289,17 @@ proc potentialValueInit(p: BProc; v: PSym; value: PNode; result: var Rope) = #echo "New code produced for ", v.name.s, " ", p.config $ value.info genBracedInit(p, value, isConst = false, v.typ, result) -proc genCppVarForCtor(p: BProc, v: PSym; vn, value: PNode; decl: var Rope) = - var params = newRopeAppender() +proc genCppParamsForCtor(p: BProc; call: PNode): string = + result = "" var argsCounter = 0 - let typ = skipTypes(value[0].typ, abstractInst) + let typ = skipTypes(call[0].typ, abstractInst) assert(typ.kind == tyProc) - for i in 1..<value.len: + for i in 1..<call.len: assert(typ.len == typ.n.len) - genOtherArg(p, value, i, typ, params, argsCounter) + genOtherArg(p, call, i, typ, result, argsCounter) + +proc genCppVarForCtor(p: BProc; call: PNode; decl: var Rope) = + let params = genCppParamsForCtor(p, call) if params.len == 0: decl = runtimeFormat("$#;\n", [decl]) else: @@ -358,7 +361,7 @@ proc genSingleVar(p: BProc, v: PSym; vn, value: PNode) = var decl = localVarDecl(p, vn) var tmp: TLoc if isCppCtorCall: - genCppVarForCtor(p, v, vn, value, decl) + genCppVarForCtor(p, value, decl) line(p, cpsStmts, decl) else: tmp = initLocExprSingleUse(p, value) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 87dcb02ad..1aed8442b 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -656,6 +656,21 @@ proc hasCppCtor(m: BModule; typ: PType): bool = if sfConstructor in prc.flags: return true +proc genCppParamsForCtor(p: BProc; call: PNode): string + +proc genCppInitializer(m: BModule, prc: BProc; typ: PType): string = + #To avoid creating a BProc per test when called inside a struct nil BProc is allowed + result = "{}" + if typ.itemId in m.g.graph.initializersPerType: + let call = m.g.graph.initializersPerType[typ.itemId] + if call != nil: + var p = prc + if p == nil: + p = BProc(module: m) + result = "{" & genCppParamsForCtor(p, call) & "}" + if prc == nil: + assert p.blocks.len == 0, "BProc belongs to a struct doesnt have blocks" + proc genRecordFieldsAux(m: BModule; n: PNode, rectype: PType, check: var IntSet; result: var Rope; unionPrefix = "") = @@ -721,7 +736,8 @@ proc genRecordFieldsAux(m: BModule; n: PNode, # don't use fieldType here because we need the # tyGenericInst for C++ template support if fieldType.isOrHasImportedCppType() or hasCppCtor(m, field.owner.typ): - result.addf("\t$1$3 $2{};$n", [getTypeDescAux(m, field.loc.t, check, dkField), sname, noAlias]) + var initializer = genCppInitializer(m, nil, fieldType) + result.addf("\t$1$3 $2$4;$n", [getTypeDescAux(m, field.loc.t, check, dkField), sname, noAlias, initializer]) else: result.addf("\t$1$3 $2;$n", [getTypeDescAux(m, field.loc.t, check, dkField), sname, noAlias]) else: internalError(m.config, n.info, "genRecordFieldsAux()") diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 2197947bf..93059600a 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -551,7 +551,8 @@ proc getTemp(p: BProc, t: PType, needsInit=false): TLoc = result = TLoc(r: "T" & rope(p.labels) & "_", k: locTemp, lode: lodeTyp t, storage: OnStack, flags: {}) if p.module.compileToCpp and isOrHasImportedCppType(t): - linefmt(p, cpsLocals, "$1 $2{};$n", [getTypeDesc(p.module, t, dkVar), result.r]) + linefmt(p, cpsLocals, "$1 $2$3;$n", [getTypeDesc(p.module, t, dkVar), result.r, + genCppInitializer(p.module, p, t)]) else: linefmt(p, cpsLocals, "$1 $2;$n", [getTypeDesc(p.module, t, dkVar), result.r]) constructLoc(p, result, not needsInit) @@ -606,7 +607,10 @@ proc assignLocalVar(p: BProc, n: PNode) = # this need not be fulfilled for inline procs; they are regenerated # for each module that uses them! let nl = if optLineDir in p.config.options: "" else: "\n" - let decl = localVarDecl(p, n) & (if p.module.compileToCpp and isOrHasImportedCppType(n.typ): "{};" else: ";") & nl + var decl = localVarDecl(p, n) + if p.module.compileToCpp and isOrHasImportedCppType(n.typ): + decl.add genCppInitializer(p.module, p, n.typ) + decl.add ";" & nl line(p, cpsLocals, decl) include ccgthreadvars @@ -640,7 +644,7 @@ proc genGlobalVarDecl(p: BProc, n: PNode; td, value: Rope; decl: var Rope) = else: decl = runtimeFormat(s.cgDeclFrmt & ";$n", [td, s.loc.r]) -proc genCppVarForCtor(p: BProc, v: PSym; vn, value: PNode; decl: var Rope) +proc genCppVarForCtor(p: BProc; call: PNode; decl: var Rope) proc callGlobalVarCppCtor(p: BProc; v: PSym; vn, value: PNode) = let s = vn.sym @@ -650,7 +654,7 @@ proc callGlobalVarCppCtor(p: BProc; v: PSym; vn, value: PNode) = let td = getTypeDesc(p.module, vn.sym.typ, dkVar) genGlobalVarDecl(p, vn, td, "", decl) decl.add " " & $s.loc.r - genCppVarForCtor(p, v, vn, value, decl) + genCppVarForCtor(p, value, decl) p.module.s[cfsVars].add decl proc assignGlobalVar(p: BProc, n: PNode; value: Rope) = diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim index b98333453..baedad0af 100644 --- a/compiler/modulegraphs.nim +++ b/compiler/modulegraphs.nim @@ -79,7 +79,8 @@ type procInstCache*: Table[ItemId, seq[LazyInstantiation]] # A symbol's ItemId. attachedOps*: array[TTypeAttachedOp, Table[ItemId, LazySym]] # Type ID, destructors, etc. methodsPerType*: Table[ItemId, seq[(int, LazySym)]] # Type ID, attached methods - memberProcsPerType*: Table[ItemId, seq[PSym]] # Type ID, attached member procs (only c++, virtual and ctor so far) + memberProcsPerType*: Table[ItemId, seq[PSym]] # Type ID, attached member procs (only c++, virtual,member and ctor so far). + initializersPerType*: Table[ItemId, PNode] # Type ID, AST call to the default ctor (c++ only) enumToStringProcs*: Table[ItemId, LazySym] emittedTypeInfo*: Table[string, FileIndex] diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 5a72632ef..006d69515 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -2077,6 +2077,53 @@ proc finishMethod(c: PContext, s: PSym) = if hasObjParam(s): methodDef(c.graph, c.idgen, s) +proc semCppMember(c: PContext; s: PSym; n: PNode) = + if sfImportc notin s.flags: + let isVirtual = sfVirtual in s.flags + let isCtor = sfConstructor in s.flags + let pragmaName = if isVirtual: "virtual" elif isCtor: "constructor" else: "member" + if c.config.backend == backendCpp: + if s.typ.len < 2 and not isCtor: + localError(c.config, n.info, pragmaName & " must have at least one parameter") + for son in s.typ: + if son!=nil and son.isMetaType: + localError(c.config, n.info, pragmaName & " unsupported for generic routine") + var typ: PType + if isCtor: + typ = s.typ[0] + if typ == nil or typ.kind != tyObject: + localError(c.config, n.info, "constructor must return an object") + else: + typ = s.typ[1] + if typ.kind == tyPtr and not isCtor: + typ = typ[0] + if typ.kind != tyObject: + localError(c.config, n.info, pragmaName & " must be either ptr to object or object type.") + if typ.owner.id == s.owner.id and c.module.id == s.owner.id: + c.graph.memberProcsPerType.mgetOrPut(typ.itemId, @[]).add s + else: + localError(c.config, n.info, + pragmaName & " procs must be defined in the same scope as the type they are virtual for and it must be a top level scope") + else: + localError(c.config, n.info, pragmaName & " procs are only supported in C++") + else: + var typ = s.typ[0] + if typ != nil and typ.kind == tyObject and typ.itemId notin c.graph.initializersPerType: + var initializerCall = newTree(nkCall, newSymNode(s)) + var isInitializer = n[paramsPos].len > 1 + for i in 1..<n[paramsPos].len: + let p = n[paramsPos][i] + let val = p[^1] + if val.kind == nkEmpty: + isInitializer = false + break + var j = 0 + while p[j].sym.kind == skParam: + initializerCall.add val + inc j + if isInitializer: + c.graph.initializersPerType[typ.itemId] = initializerCall + proc semMethodPrototype(c: PContext; s: PSym; n: PNode) = if s.isGenericRoutine: let tt = s.typ @@ -2294,35 +2341,9 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, if sfBorrow in s.flags and c.config.cmd notin cmdDocLike: result[bodyPos] = c.graph.emptyNode - if sfCppMember * s.flags != {} and sfImportc notin s.flags: - let isVirtual = sfVirtual in s.flags - let isCtor = sfConstructor in s.flags - let pragmaName = if isVirtual: "virtual" elif isCtor: "constructor" else: "member" - if c.config.backend == backendCpp: - if s.typ.len < 2 and not isCtor: - localError(c.config, n.info, pragmaName & " must have at least one parameter") - for son in s.typ: - if son!=nil and son.isMetaType: - localError(c.config, n.info, pragmaName & " unsupported for generic routine") - var typ: PType - if isCtor: - typ = s.typ[0] - if typ == nil or typ.kind != tyObject: - localError(c.config, n.info, "constructor must return an object") - else: - typ = s.typ[1] - if typ.kind == tyPtr and not isCtor: - typ = typ[0] - if typ.kind != tyObject: - localError(c.config, n.info, pragmaName & " must be either ptr to object or object type.") - if typ.owner.id == s.owner.id and c.module.id == s.owner.id: - c.graph.memberProcsPerType.mgetOrPut(typ.itemId, @[]).add s - else: - localError(c.config, n.info, - pragmaName & " procs must be defined in the same scope as the type they are virtual for and it must be a top level scope") - else: - localError(c.config, n.info, pragmaName & " procs are only supported in C++") - + if sfCppMember * s.flags != {}: + semCppMember(c, s, n) + if n[bodyPos].kind != nkEmpty and sfError notin s.flags: # for DLL generation we allow sfImportc to have a body, for use in VM if c.config.ideCmd in {ideSug, ideCon} and s.kind notin {skMacro, skTemplate} and not diff --git a/tests/cpp/tinitializers.nim b/tests/cpp/tinitializers.nim new file mode 100644 index 000000000..868cf825c --- /dev/null +++ b/tests/cpp/tinitializers.nim @@ -0,0 +1,33 @@ +discard """ + targets: "cpp" +""" + +{.emit:"""/*TYPESECTION*/ +struct CppStruct { + CppStruct(int x, char* y): x(x), y(y){} + void doSomething() {} + int x; + char* y; +}; +""".} +type + CppStruct {.importcpp, inheritable.} = object + ChildStruct = object of CppStruct + HasCppStruct = object + cppstruct: CppStruct + +proc constructCppStruct(a:cint = 5, b:cstring = "hello"): CppStruct {.importcpp: "CppStruct(@)", constructor.} +proc doSomething(this: CppStruct) {.importcpp.} +proc returnCppStruct(): CppStruct = discard +proc initChildStruct: ChildStruct = ChildStruct() +proc makeChildStruct(): ChildStruct {.constructor:"""ChildStruct(): CppStruct(5, "10")""".} = discard +proc initHasCppStruct(x: cint): HasCppStruct = + HasCppStruct(cppstruct: constructCppStruct(x)) + +proc main = + var hasCppStruct = initHasCppStruct(2) #generates cppstruct = { 10 } inside the struct + hasCppStruct.cppstruct.doSomething() + discard returnCppStruct() #generates result = { 10 } + discard initChildStruct() #generates ChildStruct temp ({}) bypassed with makeChildStruct + (proc (s:CppStruct) = discard)(CppStruct()) #CppStruct temp ({10}) +main() \ No newline at end of file |