mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-29 01:14:41 +00:00
238 lines
8.1 KiB
Nim
238 lines
8.1 KiB
Nim
#
|
|
#
|
|
# The Nimrod 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())
|
|
rangeDestructorProc*: PSym
|
|
|
|
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 == tyGenericInvokation:
|
|
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),
|
|
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),
|
|
newNode(nkDotExpr, field.info, @[holder, useSym(field)])])
|
|
|
|
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:
|
|
var caseBranch = newNode(n[i].kind, n[i].info, n[i].sons[0 .. -2])
|
|
|
|
let stmt = destroyFieldOrFields(c, n[i].lastSon, holder)
|
|
if stmt == nil:
|
|
caseBranch.addSon(newNode(nkStmtList, n[i].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: expr): stmt =
|
|
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 = skipTypes(typ, {tyConst, tyMutable}).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})
|
|
case t.kind
|
|
of tySequence, tyArray, tyArrayConstr, tyOpenArray, tyVarargs:
|
|
if instantiateDestructor(c, t.sons[0]) != nil:
|
|
if rangeDestructorProc == nil:
|
|
rangeDestructorProc = searchInScopes(c, getIdent"nimDestroyRange")
|
|
t.destructor = rangeDestructorProc
|
|
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 insertDestructors(c: PContext,
|
|
varSection: PNode): tuple[outer, inner: PNode] =
|
|
# Accepts a var or let section.
|
|
#
|
|
# When a var section has variables with destructors
|
|
# the var section is split up and finally blocks are inserted
|
|
# immediately after all "destructable" vars
|
|
#
|
|
# In case there were no destrucable variables, the proc returns
|
|
# (nil, nil) and the enclosing stmt-list requires no modifications.
|
|
#
|
|
# Otherwise, after the try blocks are created, the rest of the enclosing
|
|
# stmt-list should be inserted in the most `inner` such block (corresponding
|
|
# to the last variable).
|
|
#
|
|
# `outer` is a statement list that should replace the original var section.
|
|
# It will include the new truncated var section followed by the outermost
|
|
# try block.
|
|
let totalVars = varSection.sonsLen
|
|
for j in countup(0, totalVars - 1):
|
|
let
|
|
varId = varSection[j][0]
|
|
varTyp = varId.sym.typ
|
|
info = varId.info
|
|
|
|
if varTyp == nil or sfGlobal in varId.sym.flags: continue
|
|
let destructableT = instantiateDestructor(c, varTyp)
|
|
|
|
if destructableT != nil:
|
|
var tryStmt = newNodeI(nkTryStmt, info)
|
|
|
|
if j < totalVars - 1:
|
|
var remainingVars = newNodeI(varSection.kind, info)
|
|
remainingVars.sons = varSection.sons[(j+1)..(-1)]
|
|
let (outer, inner) = insertDestructors(c, remainingVars)
|
|
if outer != nil:
|
|
tryStmt.addSon(outer)
|
|
result.inner = inner
|
|
else:
|
|
result.inner = newNodeI(nkStmtList, info)
|
|
result.inner.addSon(remainingVars)
|
|
tryStmt.addSon(result.inner)
|
|
else:
|
|
result.inner = newNodeI(nkStmtList, info)
|
|
tryStmt.addSon(result.inner)
|
|
|
|
tryStmt.addSon(
|
|
newNode(nkFinally, info, @[
|
|
semStmt(c, newNode(nkCall, info, @[
|
|
useSym(destructableT.destructor),
|
|
useSym(varId.sym)]))]))
|
|
|
|
result.outer = newNodeI(nkStmtList, info)
|
|
varSection.sons.setLen(j+1)
|
|
result.outer.addSon(varSection)
|
|
result.outer.addSon(tryStmt)
|
|
|
|
return
|