summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorJuan M Gómez <info@jmgomez.me>2023-09-14 16:37:30 +0100
committerGitHub <noreply@github.com>2023-09-14 17:37:30 +0200
commit96e1949610bc805af451903ba5cc4d483f7b4dce (patch)
treeb99c6f5691beefdf76deb8f72f4302935ce922e9
parentac1804aba665b34a01cb014183f8fff0ba6db738 (diff)
downloadNim-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.nim15
-rw-r--r--compiler/ccgtypes.nim18
-rw-r--r--compiler/cgen.nim12
-rw-r--r--compiler/modulegraphs.nim3
-rw-r--r--compiler/semstmts.nim79
-rw-r--r--tests/cpp/tinitializers.nim33
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