mirror of
https://github.com/nim-lang/Nim.git
synced 2026-06-03 18:34:43 +00:00
implements RFC: [C++] Constructors as default initializers (#22694)
Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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()")
|
||||
|
||||
@@ -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) =
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
33
tests/cpp/tinitializers.nim
Normal file
33
tests/cpp/tinitializers.nim
Normal file
@@ -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()
|
||||
Reference in New Issue
Block a user