Skip to content

Commit

Permalink
implements RFC: [C++] Constructors as default initializers (#22694)
Browse files Browse the repository at this point in the history
Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
  • Loading branch information
jmgomez and Araq committed Sep 14, 2023
1 parent ac1804a commit 96e1949
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 41 deletions.
15 changes: 9 additions & 6 deletions compiler/ccgstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
18 changes: 17 additions & 1 deletion compiler/ccgtypes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "") =
Expand Down Expand Up @@ -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()")
Expand Down
12 changes: 8 additions & 4 deletions compiler/cgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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) =
Expand Down
3 changes: 2 additions & 1 deletion compiler/modulegraphs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
79 changes: 50 additions & 29 deletions compiler/semstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
33 changes: 33 additions & 0 deletions tests/cpp/tinitializers.nim
Original file line number Diff line number Diff line change
@@ -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()

0 comments on commit 96e1949

Please sign in to comment.