# # # The Nim Compiler # (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## This module implements destructors. # included from sem.nim # special marker values that indicates that we are # 1) AnalyzingDestructor: currently analyzing the type for destructor # generation (needed for recursive types) # 2) DestructorIsTrivial: completed the analysis before and determined # that the type has a trivial destructor var analyzingDestructor, destructorIsTrivial: PSym new(analyzingDestructor) new(destructorIsTrivial) var destructorName = getIdent"destroy_" destructorParam = getIdent"this_" destructorPragma = newIdentNode(getIdent"destructor", unknownLineInfo()) proc instantiateDestructor(c: PContext, typ: PType): PType proc doDestructorStuff(c: PContext, s: PSym, n: PNode) = var t = s.typ.sons[1].skipTypes({tyVar}) if t.kind == tyGenericInvocation: for i in 1 ..< t.sonsLen: if t.sons[i].kind != tyGenericParam: localError(n.info, errDestructorNotGenericEnough) return t = t.base elif t.kind == tyCompositeTypeClass: t = t.base if t.kind != tyGenericBody: localError(n.info, errDestructorNotGenericEnough) return t.destructor = s # automatically insert calls to base classes' destructors if n.sons[bodyPos].kind != nkEmpty: for i in countup(0, t.sonsLen - 1): # when inheriting directly from object # there will be a single nil son if t.sons[i] == nil: continue let destructableT = instantiateDestructor(c, t.sons[i]) if destructableT != nil: n.sons[bodyPos].addSon(newNode(nkCall, t.sym.info, @[ useSym(destructableT.destructor, c.graph.usageSym), n.sons[paramsPos][1][0]])) proc destroyFieldOrFields(c: PContext, field: PNode, holder: PNode): PNode proc destroySym(c: PContext, field: PSym, holder: PNode): PNode = let destructableT = instantiateDestructor(c, field.typ) if destructableT != nil: result = newNode(nkCall, field.info, @[ useSym(destructableT.destructor, c.graph.usageSym), newNode(nkDotExpr, field.info, @[holder, useSym(field, c.graph.usageSym)])]) proc destroyCase(c: PContext, n: PNode, holder: PNode): PNode = var nonTrivialFields = 0 result = newNode(nkCaseStmt, n.info, @[]) # case x.kind result.addSon(newNode(nkDotExpr, n.info, @[holder, n.sons[0]])) for i in countup(1, n.len - 1): # of A, B: let ni = n[i] var caseBranch = newNode(ni.kind, ni.info, ni.sons[0..ni.len-2]) let stmt = destroyFieldOrFields(c, ni.lastSon, holder) if stmt == nil: caseBranch.addSon(newNode(nkStmtList, ni.info, @[])) else: caseBranch.addSon(stmt) nonTrivialFields += stmt.len result.addSon(caseBranch) # maybe no fields were destroyed? if nonTrivialFields == 0: result = nil proc destroyFieldOrFields(c: PContext, field: PNode, holder: PNode): PNode = template maybeAddLine(e) = let stmt = e if stmt != nil: if result == nil: result = newNode(nkStmtList) result.addSon(stmt) case field.kind of nkRecCase: maybeAddLine destroyCase(c, field, holder) of nkSym: maybeAddLine destroySym(c, field.sym, holder) of nkRecList: for son in field: maybeAddLine destroyFieldOrFields(c, son, holder) else: internalAssert false proc generateDestructor(c: PContext, t: PType): PNode = ## generate a destructor for a user-defined object or tuple type ## returns nil if the destructor turns out to be trivial # XXX: This may be true for some C-imported types such as # Tposix_spawnattr if t.n == nil or t.n.sons == nil: return internalAssert t.n.kind == nkRecList let destructedObj = newIdentNode(destructorParam, unknownLineInfo()) # call the destructods of all fields result = destroyFieldOrFields(c, t.n, destructedObj) # base classes' destructors will be automatically called by # semProcAux for both auto-generated and user-defined destructors proc instantiateDestructor(c: PContext, typ: PType): PType = # returns nil if a variable of type `typ` doesn't require a # destructor. Otherwise, returns the type, which holds the # destructor that must be used for the varialbe. # The destructor is either user-defined or automatically # generated by the compiler in a member-wise fashion. var t = typ.skipGenericAlias let typeHoldingUserDefinition = if t.kind == tyGenericInst: t.base else: t if typeHoldingUserDefinition.destructor != nil: # XXX: This is not entirely correct for recursive types, but we need # it temporarily to hide the "destroy is already defined" problem if typeHoldingUserDefinition.destructor notin [analyzingDestructor, destructorIsTrivial]: return typeHoldingUserDefinition else: return nil t = t.skipTypes({tyGenericInst, tyAlias}) case t.kind of tySequence, tyArray, tyOpenArray, tyVarargs: t.destructor = analyzingDestructor if instantiateDestructor(c, t.sons[0]) != nil: t.destructor = getCompilerProc"nimDestroyRange" return t else: return nil of tyTuple, tyObject: t.destructor = analyzingDestructor let generated = generateDestructor(c, t) if generated != nil: internalAssert t.sym != nil var i = t.sym.info let fullDef = newNode(nkProcDef, i, @[ newIdentNode(destructorName, i), emptyNode, emptyNode, newNode(nkFormalParams, i, @[ emptyNode, newNode(nkIdentDefs, i, @[ newIdentNode(destructorParam, i), symNodeFromType(c, makeVarType(c, t), t.sym.info), emptyNode]), ]), newNode(nkPragma, i, @[destructorPragma]), emptyNode, generated ]) let semantizedDef = semProc(c, fullDef) t.destructor = semantizedDef[namePos].sym return t else: t.destructor = destructorIsTrivial return nil else: return nil proc createDestructorCall(c: PContext, s: PSym): PNode = let varTyp = s.typ if varTyp == nil or sfGlobal in s.flags: return let destructableT = instantiateDestructor(c, varTyp) if destructableT != nil: let call = semStmt(c, newNode(nkCall, s.info, @[ useSym(destructableT.destructor, c.graph.usageSym), useSym(s, c.graph.usageSym)])) result = newNode(nkDefer, s.info, @[call])