Expand hoisted default params in sem (#15270)

* Expand hoisted default params in sem
Introduce ast.newTree{I,IT}
Add test for default params in procs

* Cleanup

* Simplify hoist transformation and expand test
This commit is contained in:
Clyybber 2020-09-05 22:01:47 +02:00 • committed by GitHub
commit 35ff17410f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 178 additions and 123 deletions

View file

@ -312,10 +312,6 @@ const
# the compiler will avoid printing such names # the compiler will avoid printing such names
# in user messages. # in user messages.
sfHoisted* = sfForward
# an expression was hoised to an anonymous variable.
# the flag is applied to the var/let symbol
sfNoForward* = sfRegister sfNoForward* = sfRegister
# forward declarations are not required (per module) # forward declarations are not required (per module)
sfReorder* = sfForward sfReorder* = sfForward
@ -1121,12 +1117,49 @@ proc newNode*(kind: TNodeKind): PNode =
writeStackTrace() writeStackTrace()
inc gNodeId inc gNodeId
proc newNodeI*(kind: TNodeKind, info: TLineInfo): PNode =
result = PNode(kind: kind, info: info)
when defined(useNodeIds):
result.id = gNodeId
if result.id == nodeIdToDebug:
echo "KIND ", result.kind
writeStackTrace()
inc gNodeId
proc newNodeI*(kind: TNodeKind, info: TLineInfo, children: int): PNode =
result = PNode(kind: kind, info: info)
if children > 0:
newSeq(result.sons, children)
when defined(useNodeIds):
result.id = gNodeId
if result.id == nodeIdToDebug:
echo "KIND ", result.kind
writeStackTrace()
inc gNodeId
proc newNodeIT*(kind: TNodeKind, info: TLineInfo, typ: PType): PNode =
result = newNode(kind)
result.info = info
result.typ = typ
proc newTree*(kind: TNodeKind; children: varargs[PNode]): PNode = proc newTree*(kind: TNodeKind; children: varargs[PNode]): PNode =
result = newNode(kind) result = newNode(kind)
if children.len > 0: if children.len > 0:
result.info = children[0].info result.info = children[0].info
result.sons = @children result.sons = @children
proc newTreeI*(kind: TNodeKind; info: TLineInfo; children: varargs[PNode]): PNode =
result = newNodeI(kind, info)
if children.len > 0:
result.info = children[0].info
result.sons = @children
proc newTreeIT*(kind: TNodeKind; info: TLineInfo; typ: PType; children: varargs[PNode]): PNode =
result = newNodeIT(kind, info, typ)
if children.len > 0:
result.info = children[0].info
result.sons = @children
template previouslyInferred*(t: PType): PType = template previouslyInferred*(t: PType): PType =
if t.sons.len > 1: t.lastSon else: nil if t.sons.len > 1: t.lastSon else: nil
@ -1227,43 +1260,6 @@ proc newSymNode*(sym: PSym, info: TLineInfo): PNode =
result.typ = sym.typ result.typ = sym.typ
result.info = info result.info = info
proc newNodeI*(kind: TNodeKind, info: TLineInfo): PNode =
result = PNode(kind: kind, info: info)
when defined(useNodeIds):
result.id = gNodeId
if result.id == nodeIdToDebug:
echo "KIND ", result.kind
writeStackTrace()
inc gNodeId
proc newNodeI*(kind: TNodeKind, info: TLineInfo, children: int): PNode =
result = PNode(kind: kind, info: info)
if children > 0:
newSeq(result.sons, children)
when defined(useNodeIds):
result.id = gNodeId
if result.id == nodeIdToDebug:
echo "KIND ", result.kind
writeStackTrace()
inc gNodeId
proc newNode*(kind: TNodeKind, info: TLineInfo, sons: TNodeSeq = @[],
typ: PType = nil): PNode =
# XXX use shallowCopy here for ownership transfer:
result = PNode(kind: kind, info: info, typ: typ)
result.sons = sons
when defined(useNodeIds):
result.id = gNodeId
if result.id == nodeIdToDebug:
echo "KIND ", result.kind
writeStackTrace()
inc gNodeId
proc newNodeIT*(kind: TNodeKind, info: TLineInfo, typ: PType): PNode =
result = newNode(kind)
result.info = info
result.typ = typ
proc newIntNode*(kind: TNodeKind, intVal: BiggestInt): PNode = proc newIntNode*(kind: TNodeKind, intVal: BiggestInt): PNode =
result = newNode(kind) result = newNode(kind)
result.intVal = intVal result.intVal = intVal

View file

@ -992,8 +992,8 @@ proc documentEffect(cache: IdentCache; n, x: PNode, effectType: TSpecialWord, id
# set the type so that the following analysis doesn't screw up: # set the type so that the following analysis doesn't screw up:
effects[i].typ = real[i].typ effects[i].typ = real[i].typ
result = newNode(nkExprColonExpr, n.info, @[ result = newTreeI(nkExprColonExpr, n.info,
newIdentNode(getIdent(cache, specialWords[effectType]), n.info), effects]) newIdentNode(getIdent(cache, specialWords[effectType]), n.info), effects)
proc documentWriteEffect(cache: IdentCache; n: PNode; flag: TSymFlag; pragmaName: string): PNode = proc documentWriteEffect(cache: IdentCache; n: PNode; flag: TSymFlag; pragmaName: string): PNode =
let s = n[namePos].sym let s = n[namePos].sym
@ -1005,8 +1005,8 @@ proc documentWriteEffect(cache: IdentCache; n: PNode; flag: TSymFlag; pragmaName
effects.add params[i] effects.add params[i]
if effects.len > 0: if effects.len > 0:
result = newNode(nkExprColonExpr, n.info, @[ result = newTreeI(nkExprColonExpr, n.info,
newIdentNode(getIdent(cache, pragmaName), n.info), effects]) newIdentNode(getIdent(cache, pragmaName), n.info), effects)
proc documentRaises*(cache: IdentCache; n: PNode) = proc documentRaises*(cache: IdentCache; n: PNode) =
if n[namePos].kind != nkSym: return if n[namePos].kind != nkSym: return

View file

@ -369,14 +369,3 @@ proc genLen*(g: ModuleGraph; n: PNode): PNode =
result[0] = newSymNode(getSysMagic(g, n.info, "len", mLengthSeq)) result[0] = newSymNode(getSysMagic(g, n.info, "len", mLengthSeq))
result[1] = n result[1] = n
proc hoistExpr*(varSection, expr: PNode, name: PIdent, owner: PSym): PSym =
result = newSym(skLet, name, owner, varSection.info, owner.options)
result.flags.incl sfHoisted
result.typ = expr.typ
var varDef = newNodeI(nkIdentDefs, varSection.info, 3)
varDef[0] = newSymNode(result)
varDef[1] = newNodeI(nkEmpty, varSection.info)
varDef[2] = expr
varSection.add varDef

View file

@ -1444,7 +1444,7 @@ proc parseExprStmt(p: var Parser): PNode =
result.add(commandParam(p, isFirstParam, pmNormal)) result.add(commandParam(p, isFirstParam, pmNormal))
if p.tok.tokType != tkComma: break if p.tok.tokType != tkComma: break
elif p.tok.indent < 0 and isExprStart(p): elif p.tok.indent < 0 and isExprStart(p):
result = newNode(nkCommand, a.info, @[a]) result = newTreeI(nkCommand, a.info, a)
while true: while true:
result.add(commandParam(p, isFirstParam, pmNormal)) result.add(commandParam(p, isFirstParam, pmNormal))
if p.tok.tokType != tkComma: break if p.tok.tokType != tkComma: break

View file

@ -532,7 +532,7 @@ proc processLink(c: PContext, n: PNode) =
proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode = proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode =
case n[1].kind case n[1].kind
of nkStrLit, nkRStrLit, nkTripleStrLit: of nkStrLit, nkRStrLit, nkTripleStrLit:
result = newNode(if n.kind == nkAsmStmt: nkAsmStmt else: nkArgList, n.info) result = newNodeI(if n.kind == nkAsmStmt: nkAsmStmt else: nkArgList, n.info)
var str = n[1].strVal var str = n[1].strVal
if str == "": if str == "":
localError(con.config, n.info, "empty 'asm' statement") localError(con.config, n.info, "empty 'asm' statement")
@ -563,7 +563,7 @@ proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode =
a = c + 1 a = c + 1
else: else:
illFormedAstLocal(n, con.config) illFormedAstLocal(n, con.config)
result = newNode(nkAsmStmt, n.info) result = newNodeI(nkAsmStmt, n.info)
proc pragmaEmit(c: PContext, n: PNode) = proc pragmaEmit(c: PContext, n: PNode) =
if n.kind notin nkPragmaCallKinds or n.len != 2: if n.kind notin nkPragmaCallKinds or n.len != 2:
@ -626,7 +626,7 @@ proc processPragma(c: PContext, n: PNode, i: int) =
invalidPragma(c, n) invalidPragma(c, n)
var userPragma = newSym(skTemplate, it[1].ident, nil, it.info, c.config.options) var userPragma = newSym(skTemplate, it[1].ident, nil, it.info, c.config.options)
userPragma.ast = newNode(nkPragma, n.info, n.sons[i+1..^1]) userPragma.ast = newTreeI(nkPragma, n.info, n.sons[i+1..^1])
strTableAdd(c.userPragmas, userPragma) strTableAdd(c.userPragmas, userPragma)
proc pragmaRaisesOrTags(c: PContext, n: PNode) = proc pragmaRaisesOrTags(c: PContext, n: PNode) =

View file

@ -381,8 +381,7 @@ proc makeNotType*(c: PContext, t1: PType): PType =
result.flags.incl tfHasMeta result.flags.incl tfHasMeta
proc nMinusOne(c: PContext; n: PNode): PNode = proc nMinusOne(c: PContext; n: PNode): PNode =
result = newNode(nkCall, n.info, @[ result = newTreeI(nkCall, n.info, newSymNode(getSysMagic(c.graph, n.info, "pred", mPred)), n)
newSymNode(getSysMagic(c.graph, n.info, "pred", mPred)), n])
# Remember to fix the procs below this one when you make changes! # Remember to fix the procs below this one when you make changes!
proc makeRangeWithStaticExpr*(c: PContext, n: PNode): PType = proc makeRangeWithStaticExpr*(c: PContext, n: PNode): PType =
@ -391,9 +390,8 @@ proc makeRangeWithStaticExpr*(c: PContext, n: PNode): PType =
result.sons = @[intType] result.sons = @[intType]
if n.typ != nil and n.typ.n == nil: if n.typ != nil and n.typ.n == nil:
result.flags.incl tfUnresolved result.flags.incl tfUnresolved
result.n = newNode(nkRange, n.info, @[ result.n = newTreeI(nkRange, n.info, newIntTypeNode(0, intType),
newIntTypeNode(0, intType), makeStaticExpr(c, nMinusOne(c, n)))
makeStaticExpr(c, nMinusOne(c, n))])
template rangeHasUnresolvedStatic*(t: PType): bool = template rangeHasUnresolvedStatic*(t: PType): bool =
tfUnresolved in t.flags tfUnresolved in t.flags

View file

@ -1556,10 +1556,9 @@ proc propertyWriteAccess(c: PContext, n, nOrig, a: PNode): PNode =
# this is ugly. XXX Semantic checking should use the ``nfSem`` flag for # this is ugly. XXX Semantic checking should use the ``nfSem`` flag for
# nodes? # nodes?
let aOrig = nOrig[0] let aOrig = nOrig[0]
result = newNode(nkCall, n.info, sons = @[setterId, a[0], result = newTreeI(nkCall, n.info, setterId, a[0], semExprWithType(c, n[1]))
semExprWithType(c, n[1])])
result.flags.incl nfDotSetter result.flags.incl nfDotSetter
let orig = newNode(nkCall, n.info, sons = @[setterId, aOrig[0], nOrig[1]]) let orig = newTreeI(nkCall, n.info, setterId, aOrig[0], nOrig[1])
result = semOverloadedCallAnalyseEffects(c, result, orig, {}) result = semOverloadedCallAnalyseEffects(c, result, orig, {})
if result != nil: if result != nil:
@ -2041,7 +2040,7 @@ proc semQuoteAst(c: PContext, n: PNode): PNode =
dummyTemplate[paramsPos].add getSysSym(c.graph, n.info, "untyped").newSymNode # return type dummyTemplate[paramsPos].add getSysSym(c.graph, n.info, "untyped").newSymNode # return type
ids.add getSysSym(c.graph, n.info, "untyped").newSymNode # params type ids.add getSysSym(c.graph, n.info, "untyped").newSymNode # params type
ids.add c.graph.emptyNode # no default value ids.add c.graph.emptyNode # no default value
dummyTemplate[paramsPos].add newNode(nkIdentDefs, n.info, ids) dummyTemplate[paramsPos].add newTreeI(nkIdentDefs, n.info, ids)
var tmpl = semTemplateDef(c, dummyTemplate) var tmpl = semTemplateDef(c, dummyTemplate)
quotes[0] = tmpl[namePos] quotes[0] = tmpl[namePos]
@ -2053,10 +2052,10 @@ proc semQuoteAst(c: PContext, n: PNode): PNode =
newIdentNode(getIdent(c.cache, "newIdentNode"), n.info) newIdentNode(getIdent(c.cache, "newIdentNode"), n.info)
else: else:
identNodeSym.newSymNode identNodeSym.newSymNode
quotes[1] = newNode(nkCall, n.info, @[identNode, newStrNode(nkStrLit, "result")]) quotes[1] = newTreeI(nkCall, n.info, identNode, newStrNode(nkStrLit, "result"))
result = newNode(nkCall, n.info, @[ result = newTreeI(nkCall, n.info,
createMagic(c.graph, "getAst", mExpandToAst).newSymNode, createMagic(c.graph, "getAst", mExpandToAst).newSymNode,
newNode(nkCall, n.info, quotes)]) newTreeI(nkCall, n.info, quotes))
result = semExpandToAst(c, result) result = semExpandToAst(c, result)
proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
@ -2579,6 +2578,43 @@ proc shouldBeBracketExpr(n: PNode): bool =
n[0] = be n[0] = be
return true return true
proc hoistParamsUsedInDefault(c: PContext, call, letSection, defExpr: var PNode) =
# This takes care of complicated signatures such as:
# proc foo(a: int, b = a)
# proc bar(a: int, b: int, c = a + b)
#
# The recursion may confuse you. It performs two duties:
#
# 1) extracting all referenced params from default expressions
# into a let section preceding the call
#
# 2) replacing the "references" within the default expression
# with these extracted skLet symbols.
#
# The first duty is carried out directly in the code here, while the second
# duty is activated by returning a non-nil value. The caller is responsible
# for replacing the input to the function with the returned non-nil value.
# (which is the hoisted symbol)
if defExpr.kind == nkSym and defExpr.sym.kind == skParam and defExpr.sym.owner == call[0].sym:
let paramPos = defExpr.sym.position + 1
if call[paramPos].kind != nkSym:
let hoistedVarSym = newSym(skLet, getIdent(c.graph.cache, genPrefix), c.p.owner, letSection.info, c.p.owner.options)
hoistedVarSym.typ = call[paramPos].typ
letSection.add newTreeI(nkIdentDefs, letSection.info,
newSymNode(hoistedVarSym),
newNodeI(nkEmpty, letSection.info),
call[paramPos])
call[paramPos] = newSymNode(hoistedVarSym) # Refer the original arg to its hoisted sym
# arg we refer to is a sym, wether introduced by hoisting or not doesn't matter, we simply reuse it
defExpr = call[paramPos]
else:
for i in 0..<defExpr.safeLen:
hoistParamsUsedInDefault(c, call, letSection, defExpr[i])
proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
when defined(nimCompilerStackraceHints): when defined(nimCompilerStackraceHints):
setFrameMsg c.config$n.info & " " & $n.kind setFrameMsg c.config$n.info & " " & $n.kind
@ -2707,6 +2743,15 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
result = semDirectOp(c, n, flags) result = semDirectOp(c, n, flags)
else: else:
result = semIndirectOp(c, n, flags) result = semIndirectOp(c, n, flags)
if nfDefaultRefsParam in result.flags:
result = result.copyTree #XXX: Figure out what causes default param nodes to be shared.. (sigmatch bug?)
# We've found a default value that references another param.
# See the notes in `hoistParamsUsedInDefault` for more details.
var hoistedParams = newNodeI(nkLetSection, result.info)
for i in 1..<result.len:
hoistParamsUsedInDefault(c, result, hoistedParams, result[i])
result = newTreeIT(nkStmtListExpr, result.info, result.typ, hoistedParams, result)
of nkWhen: of nkWhen:
if efWantStmt in flags: if efWantStmt in flags:
result = semWhen(c, n, true) result = semWhen(c, n, true)

View file

@ -1281,7 +1281,7 @@ proc trackStmt*(c: PContext; module: PSym; n: PNode, isTopLevel: bool) =
nkTypeSection, nkConverterDef, nkMethodDef, nkIteratorDef}: nkTypeSection, nkConverterDef, nkMethodDef, nkIteratorDef}:
return return
let g = c.graph let g = c.graph
var effects = newNode(nkEffectList, n.info) var effects = newNodeI(nkEffectList, n.info)
var t: TEffects var t: TEffects
initEffects(g, effects, module, t, c) initEffects(g, effects, module, t, c)
t.isTopLevel = isTopLevel t.isTopLevel = isTopLevel

View file

@ -207,7 +207,7 @@ proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode; start=0): PNode =
result.sym = replaceTypeVarsS(cl, n.sym) result.sym = replaceTypeVarsS(cl, n.sym)
if result.sym.typ.kind == tyVoid: if result.sym.typ.kind == tyVoid:
# don't add the 'void' field # don't add the 'void' field
result = newNode(nkRecList, n.info) result = newNodeI(nkRecList, n.info)
of nkRecWhen: of nkRecWhen:
var branch: PNode = nil # the branch to take var branch: PNode = nil # the branch to take
for i in 0..<n.len: for i in 0..<n.len:

View file

@ -886,43 +886,6 @@ proc commonOptimizations*(g: ModuleGraph; c: PSym, n: PNode): PNode =
else: else:
result = n result = n
proc hoistParamsUsedInDefault(c: PTransf, call, letSection, defExpr: PNode): PNode =
# This takes care of complicated signatures such as:
# proc foo(a: int, b = a)
# proc bar(a: int, b: int, c = a + b)
#
# The recursion may confuse you. It performs two duties:
#
# 1) extracting all referenced params from default expressions
# into a let section preceding the call
#
# 2) replacing the "references" within the default expression
# with these extracted skLet symbols.
#
# The first duty is carried out directly in the code here, while the second
# duty is activated by returning a non-nil value. The caller is responsible
# for replacing the input to the function with the returned non-nil value.
# (which is the hoisted symbol)
if defExpr.kind == nkSym:
if defExpr.sym.kind == skParam and defExpr.sym.owner == call[0].sym:
let paramPos = defExpr.sym.position + 1
if call[paramPos].kind == nkSym and sfHoisted in call[paramPos].sym.flags:
# Already hoisted, we still need to return it in order to replace the
# placeholder expression in the default value.
return call[paramPos]
let hoistedVarSym = hoistExpr(letSection,
call[paramPos],
getIdent(c.graph.cache, genPrefix),
c.transCon.owner).newSymNode
call[paramPos] = hoistedVarSym
return hoistedVarSym
else:
for i in 0..<defExpr.safeLen:
let hoisted = hoistParamsUsedInDefault(c, call, letSection, defExpr[i])
if hoisted != nil: defExpr[i] = hoisted
proc transform(c: PTransf, n: PNode): PNode = proc transform(c: PTransf, n: PNode): PNode =
when false: when false:
var oldDeferAnchor: PNode var oldDeferAnchor: PNode
@ -989,16 +952,6 @@ proc transform(c: PTransf, n: PNode): PNode =
of nkBreakStmt: result = transformBreak(c, n) of nkBreakStmt: result = transformBreak(c, n)
of nkCallKinds: of nkCallKinds:
result = transformCall(c, n) result = transformCall(c, n)
var call = result
if nfDefaultRefsParam in call.flags:
# We've found a default value that references another param.
# See the notes in `hoistParamsUsedInDefault` for more details.
var hoistedParams = newNodeI(nkLetSection, call.info, 0)
for i in 1..<call.len:
let hoisted = hoistParamsUsedInDefault(c, call, hoistedParams, call[i])
if hoisted != nil: call[i] = hoisted
result = newTree(nkStmtListExpr, hoistedParams, call)
result.typ = call.typ
of nkAddr, nkHiddenAddr: of nkAddr, nkHiddenAddr:
result = transformAddrDeref(c, n, nkDerefExpr, nkHiddenDeref) result = transformAddrDeref(c, n, nkDerefExpr, nkHiddenDeref)
of nkDerefExpr, nkHiddenDeref: of nkDerefExpr, nkHiddenDeref:

View file

@ -19,8 +19,7 @@ proc opSlurp*(file: string, info: TLineInfo, module: PSym; conf: ConfigRef): str
# we produce a fake include statement for every slurped filename, so that # we produce a fake include statement for every slurped filename, so that
# the module dependencies are accurate: # the module dependencies are accurate:
discard conf.fileInfoIdx(AbsoluteFile filename) discard conf.fileInfoIdx(AbsoluteFile filename)
appendToModule(module, newNode(nkIncludeStmt, info, @[ appendToModule(module, newTreeI(nkIncludeStmt, info, newStrNode(nkStrLit, filename)))
newStrNode(nkStrLit, filename)]))
except IOError: except IOError:
localError(conf, info, "cannot open file: " & file) localError(conf, info, "cannot open file: " & file)
result = "" result = ""

View file

@ -1,8 +1,83 @@
discard """ discard """
output: ''' output: '''
hi hi
hi
topLevel|topLevel|
topLevel2|topLevel2|
inProc|inProc|
inProc2|inProc2|
topLevel|9
topLevel2|10
inProc|7
inProc2|8
must have been the wind..
I'm there
must have been the wind..
I'm there
symbol'a'symbol'a'
symbol'b'symbol'b'
symbol'a'symbol'b'
symbol'a'9
symbol'b'9
symbol'a'0
''' '''
""" """
import mdefaultprocparam import mdefaultprocparam
p() p()
proc testP =
p()
testP()
proc p2(s: string, count = s): string = s & count
proc testP2 =
echo p2 """inProc|"""
echo p2 """inProc2|"""
echo p2 """topLevel|"""
echo p2 """topLevel2|"""
testP2()
import macros
macro dTT(a: typed) = echo a.treeRepr
proc p3(s: string, count = len(s)): string = s & $count
proc testP3 =
echo p3 """inProc|"""
echo p3 """inProc2|"""
echo p3 """topLevel|"""
echo p3 """topLevel2|"""
testP3()
proc cut(s: string, c = len(s)): string =
s[0..<s.len-c]
echo "must have been the wind.." & cut "I'm gone"
echo cut("I'm gone", 4) & "there"
proc testCut =
echo "must have been the wind.." & cut "I'm gone"
echo cut("I'm gone", 4) & "there"
testCut()
var a = "symbol'a'"
var b = "symbol'b'"
block:
echo p2(a)
block:
echo p2(b)
block:
echo p2(a, b)
block:
echo p3(a)
echo p3(b)
echo p3(a, 0)