summary refs log tree commit diff stats
path: root/compiler/parampatterns.nim
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/parampatterns.nim')
-rw-r--r--compiler/parampatterns.nim212
1 files changed, 139 insertions, 73 deletions
diff --git a/compiler/parampatterns.nim b/compiler/parampatterns.nim
index 05b2d8f9c..e8ec22fe1 100644
--- a/compiler/parampatterns.nim
+++ b/compiler/parampatterns.nim
@@ -10,7 +10,10 @@
 ## This module implements the pattern matching features for term rewriting
 ## macro support.
 
-import strutils, ast, astalgo, types, msgs, idents, renderer, wordrecg, trees
+import ast, types, msgs, idents, renderer, wordrecg, trees,
+  options
+
+import std/strutils
 
 # we precompile the pattern here for efficiency into some internal
 # stack based VM :-) Why? Because it's fun; I did no benchmarks to see if that
@@ -41,11 +44,11 @@ type
 const
   MaxStackSize* = 64 ## max required stack size by the VM
 
-proc patternError(n: PNode) =
-  localError(n.info, errIllFormedAstX, renderTree(n, {renderNoComments}))
+proc patternError(n: PNode; conf: ConfigRef) =
+  localError(conf, n.info, "illformed AST: " & renderTree(n, {renderNoComments}))
 
 proc add(code: var TPatternCode, op: TOpcode) {.inline.} =
-  add(code, chr(ord(op)))
+  code.add chr(ord(op))
 
 proc whichAlias*(p: PSym): TAliasRequest =
   if p.constraint != nil:
@@ -53,42 +56,42 @@ proc whichAlias*(p: PSym): TAliasRequest =
   else:
     result = aqNone
 
-proc compileConstraints(p: PNode, result: var TPatternCode) =
+proc compileConstraints(p: PNode, result: var TPatternCode; conf: ConfigRef) =
   case p.kind
   of nkCallKinds:
-    if p.sons[0].kind != nkIdent:
-      patternError(p.sons[0])
+    if p[0].kind != nkIdent:
+      patternError(p[0], conf)
       return
-    let op = p.sons[0].ident
+    let op = p[0].ident
     if p.len == 3:
       if op.s == "|" or op.id == ord(wOr):
-        compileConstraints(p.sons[1], result)
-        compileConstraints(p.sons[2], result)
+        compileConstraints(p[1], result, conf)
+        compileConstraints(p[2], result, conf)
         result.add(ppOr)
       elif op.s == "&" or op.id == ord(wAnd):
-        compileConstraints(p.sons[1], result)
-        compileConstraints(p.sons[2], result)
+        compileConstraints(p[1], result, conf)
+        compileConstraints(p[2], result, conf)
         result.add(ppAnd)
       else:
-        patternError(p)
+        patternError(p, conf)
     elif p.len == 2 and (op.s == "~" or op.id == ord(wNot)):
-      compileConstraints(p.sons[1], result)
+      compileConstraints(p[1], result, conf)
       result.add(ppNot)
     else:
-      patternError(p)
+      patternError(p, conf)
   of nkAccQuoted, nkPar:
     if p.len == 1:
-      compileConstraints(p.sons[0], result)
+      compileConstraints(p[0], result, conf)
     else:
-      patternError(p)
+      patternError(p, conf)
   of nkIdent:
     let spec = p.ident.s.normalize
     case spec
-    of "atom":  result.add(ppAtom)
-    of "lit":   result.add(ppLit)
-    of "sym":   result.add(ppSym)
+    of "atom": result.add(ppAtom)
+    of "lit": result.add(ppLit)
+    of "sym": result.add(ppSym)
     of "ident": result.add(ppIdent)
-    of "call":  result.add(ppCall)
+    of "call": result.add(ppCall)
     of "alias": result[0] = chr(aqShouldAlias.ord)
     of "noalias": result[0] = chr(aqNoAlias.ord)
     of "lvalue": result.add(ppLValue)
@@ -97,37 +100,36 @@ proc compileConstraints(p: PNode, result: var TPatternCode) =
     of "nosideeffect": result.add(ppNoSideEffect)
     else:
       # check all symkinds:
-      internalAssert int(high(TSymKind)) < 255
-      for i in low(TSymKind)..high(TSymKind):
-        if cmpIgnoreStyle(($i).substr(2), spec) == 0:
+      internalAssert conf, int(high(TSymKind)) < 255
+      for i in TSymKind:
+        if cmpIgnoreStyle(i.toHumanStr, spec) == 0:
           result.add(ppSymKind)
           result.add(chr(i.ord))
           return
       # check all nodekinds:
-      internalAssert int(high(TNodeKind)) < 255
-      for i in low(TNodeKind)..high(TNodeKind):
+      internalAssert conf, int(high(TNodeKind)) < 255
+      for i in TNodeKind:
         if cmpIgnoreStyle($i, spec) == 0:
           result.add(ppNodeKind)
           result.add(chr(i.ord))
           return
-      patternError(p)
+      patternError(p, conf)
   else:
-    patternError(p)
+    patternError(p, conf)
 
-proc semNodeKindConstraints*(p: PNode): PNode =
+proc semNodeKindConstraints*(n: PNode; conf: ConfigRef; start: Natural): PNode =
   ## does semantic checking for a node kind pattern and compiles it into an
   ## efficient internal format.
-  assert p.kind == nkCurlyExpr
-  result = newNodeI(nkStrLit, p.info)
+  result = newNodeI(nkStrLit, n.info)
   result.strVal = newStringOfCap(10)
   result.strVal.add(chr(aqNone.ord))
-  if p.len >= 2:
-    for i in 1.. <p.len:
-      compileConstraints(p.sons[i], result.strVal)
+  if n.len >= 2:
+    for i in start..<n.len:
+      compileConstraints(n[i], result.strVal, conf)
     if result.strVal.len > MaxStackSize-1:
-      internalError(p.info, "parameter pattern too complex")
+      internalError(conf, n.info, "parameter pattern too complex")
   else:
-    patternError(p)
+    patternError(n, conf)
   result.strVal.add(ppEof)
 
 type
@@ -138,13 +140,16 @@ proc checkForSideEffects*(n: PNode): TSideEffectAnalysis =
   case n.kind
   of nkCallKinds:
     # only calls can produce side effects:
-    let op = n.sons[0]
+    let op = n[0]
     if op.kind == nkSym and isRoutine(op.sym):
       let s = op.sym
       if sfSideEffect in s.flags:
         return seSideEffect
-      # assume no side effect:
-      result = seNoSideEffect
+      elif tfNoSideEffect in op.typ.flags:
+        result = seNoSideEffect
+      else:
+        # assume side effect:
+        result = seSideEffect
     elif tfNoSideEffect in op.typ.flags:
       # indirect call without side effects:
       result = seNoSideEffect
@@ -152,8 +157,8 @@ proc checkForSideEffects*(n: PNode): TSideEffectAnalysis =
       # indirect call: assume side effect:
       return seSideEffect
     # we need to check n[0] too: (FwithSideEffectButReturnsProcWithout)(args)
-    for i in 0 .. <n.len:
-      let ret = checkForSideEffects(n.sons[i])
+    for i in 0..<n.len:
+      let ret = checkForSideEffects(n[i])
       if ret == seSideEffect: return ret
       elif ret == seUnknown and result == seNoSideEffect:
         result = seUnknown
@@ -163,8 +168,8 @@ proc checkForSideEffects*(n: PNode): TSideEffectAnalysis =
   else:
     # assume no side effect:
     result = seNoSideEffect
-    for i in 0 .. <n.len:
-      let ret = checkForSideEffects(n.sons[i])
+    for i in 0..<n.len:
+      let ret = checkForSideEffects(n[i])
       if ret == seSideEffect: return ret
       elif ret == seUnknown and result == seNoSideEffect:
         result = seUnknown
@@ -176,65 +181,126 @@ type
     arLocalLValue,            # is an l-value, but local var; must not escape
                               # its stack frame!
     arDiscriminant,           # is a discriminant
+    arAddressableConst,       # an addressable const
+    arLentValue,              # lent value
     arStrange                 # it is a strange beast like 'typedesc[var T]'
 
-proc isAssignable*(owner: PSym, n: PNode; isUnsafeAddr=false): TAssignableResult =
+proc exprRoot*(n: PNode; allowCalls = true): PSym =
+  result = nil
+  var it = n
+  while true:
+    case it.kind
+    of nkSym: return it.sym
+    of nkHiddenDeref, nkDerefExpr:
+      if it[0].typ.skipTypes(abstractInst).kind in {tyPtr, tyRef}:
+        # 'ptr' is unsafe anyway and 'ref' is always on the heap,
+        # so allow these derefs:
+        break
+      else:
+        it = it[0]
+    of nkDotExpr, nkBracketExpr, nkHiddenAddr,
+       nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr:
+      it = it[0]
+    of nkHiddenStdConv, nkHiddenSubConv, nkConv:
+      it = it[1]
+    of nkStmtList, nkStmtListExpr:
+      if it.len > 0 and it.typ != nil: it = it.lastSon
+      else: break
+    of nkCallKinds:
+      if allowCalls and it.typ != nil and it.typ.kind in {tyVar, tyLent} and it.len > 1:
+        # See RFC #7373, calls returning 'var T' are assumed to
+        # return a view into the first argument (if there is one):
+        it = it[1]
+      else:
+        break
+    else:
+      break
+
+proc isAssignable*(owner: PSym, n: PNode): TAssignableResult =
   ## 'owner' can be nil!
   result = arNone
   case n.kind
   of nkEmpty:
-    if n.typ != nil and n.typ.kind == tyVar:
+    if n.typ != nil and n.typ.kind in {tyVar}:
       result = arLValue
   of nkSym:
-    let kinds = if isUnsafeAddr: {skVar, skResult, skTemp, skParam, skLet}
-                else: {skVar, skResult, skTemp}
-    if n.sym.kind in kinds:
-      if owner != nil and owner.id == n.sym.owner.id and
-          sfGlobal notin n.sym.flags:
-        result = arLocalLValue
+    const kinds = {skVar, skResult, skTemp, skParam, skLet, skForVar}
+    if n.sym.kind == skParam:
+      result = if n.sym.typ.kind in {tyVar, tySink}: arLValue else: arAddressableConst
+    elif n.sym.kind == skConst and dontInlineConstant(n, n.sym.astdef):
+      result = arAddressableConst
+    elif n.sym.kind in kinds:
+      if n.sym.kind in {skParam, skLet, skForVar}:
+        result = arAddressableConst
       else:
-        result = arLValue
-    elif n.sym.kind == skParam and n.sym.typ.kind == tyVar:
-      result = arLValue
+        if owner != nil and owner == n.sym.owner and
+            sfGlobal notin n.sym.flags:
+          result = arLocalLValue
+        else:
+          result = arLValue
     elif n.sym.kind == skType:
       let t = n.sym.typ.skipTypes({tyTypeDesc})
-      if t.kind == tyVar: result = arStrange
+      if t.kind in {tyVar}: result = arStrange
   of nkDotExpr:
-    if skipTypes(n.sons[0].typ, abstractInst-{tyTypeDesc}).kind in
-        {tyVar, tyPtr, tyRef}:
+    let t = skipTypes(n[0].typ, abstractInst-{tyTypeDesc})
+    if t.kind in {tyVar, tySink, tyPtr, tyRef}:
       result = arLValue
+    elif t.kind == tyLent:
+      result = arAddressableConst
     else:
-      result = isAssignable(owner, n.sons[0], isUnsafeAddr)
-    if result != arNone and sfDiscriminant in n.sons[1].sym.flags:
+      result = isAssignable(owner, n[0])
+    if result != arNone and n[1].kind == nkSym and
+        sfDiscriminant in n[1].sym.flags:
       result = arDiscriminant
   of nkBracketExpr:
-    if skipTypes(n.sons[0].typ, abstractInst-{tyTypeDesc}).kind in
-        {tyVar, tyPtr, tyRef}:
+    let t = skipTypes(n[0].typ, abstractInst-{tyTypeDesc})
+    if t.kind in {tyVar, tySink, tyPtr, tyRef}:
       result = arLValue
+    elif t.kind == tyLent:
+      result = arAddressableConst
     else:
-      result = isAssignable(owner, n.sons[0], isUnsafeAddr)
+      result = isAssignable(owner, n[0])
   of nkHiddenStdConv, nkHiddenSubConv, nkConv:
     # Object and tuple conversions are still addressable, so we skip them
     # XXX why is 'tyOpenArray' allowed here?
     if skipTypes(n.typ, abstractPtrs-{tyTypeDesc}).kind in
         {tyOpenArray, tyTuple, tyObject}:
-      result = isAssignable(owner, n.sons[1], isUnsafeAddr)
-    elif compareTypes(n.typ, n.sons[1].typ, dcEqIgnoreDistinct):
+      result = isAssignable(owner, n[1])
+    elif compareTypes(n.typ, n[1].typ, dcEqIgnoreDistinct, {IgnoreRangeShallow}):
       # types that are equal modulo distinction preserve l-value:
-      result = isAssignable(owner, n.sons[1], isUnsafeAddr)
-  of nkHiddenDeref, nkDerefExpr, nkHiddenAddr:
+      result = isAssignable(owner, n[1])
+  of nkHiddenDeref:
+    let n0 = n[0]
+    if n0.typ.kind == tyLent:
+      if n0.kind == nkSym and n0.sym.kind == skResult:
+        result = arLValue
+      else:
+        result = arLentValue
+    else:
+      result = arLValue
+  of nkDerefExpr, nkHiddenAddr:
     result = arLValue
   of nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr:
-    result = isAssignable(owner, n.sons[0], isUnsafeAddr)
+    result = isAssignable(owner, n[0])
   of nkCallKinds:
-    # builtin slice keeps lvalue-ness:
-    if getMagic(n) in {mArrGet, mSlice}:
-      result = isAssignable(owner, n.sons[1], isUnsafeAddr)
-    elif n.typ != nil and n.typ.kind == tyVar:
-      result = arLValue
+    let m = getMagic(n)
+    if m == mSlice:
+      # builtin slice keeps l-value-ness
+      # except for pointers because slice dereferences
+      if n[1].typ.kind == tyPtr:
+        result = arLValue
+      else:
+        result = isAssignable(owner, n[1])
+    elif m == mArrGet:
+      result = isAssignable(owner, n[1])
+    elif n.typ != nil:
+      case n.typ.kind
+      of tyVar: result = arLValue
+      of tyLent: result = arLentValue
+      else: discard
   of nkStmtList, nkStmtListExpr:
     if n.typ != nil:
-      result = isAssignable(owner, n.lastSon, isUnsafeAddr)
+      result = isAssignable(owner, n.lastSon)
   of nkVarTy:
     # XXX: The fact that this is here is a bit of a hack.
     # The goal is to allow the use of checks such as "foo(var T)"