summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2018-04-30 11:16:56 +0200
committerAraq <rumpf_a@web.de>2018-04-30 11:16:56 +0200
commited79201d0b9710efb9ad89ab6ecbe7eff1742516 (patch)
treec7fe5485f63f92a16e50d1d34d666bdb35bbd95b
parentde8526ff0419bb850c4bdd4251c90dd926f48918 (diff)
downloadNim-ed79201d0b9710efb9ad89ab6ecbe7eff1742516.tar.gz
make more tests green
-rw-r--r--lib/pure/encodings.nim2
-rw-r--r--lib/pure/ospaths.nim2
-rw-r--r--tests/concepts/tstackconcept.nim2
-rw-r--r--tests/stdlib/twchartoutf8.nim1
-rw-r--r--tests/vm/tcompiletimetable.nim1
5 files changed, 3 insertions, 5 deletions
diff --git a/lib/pure/encodings.nim b/lib/pure/encodings.nim
index 5840d443d..731fbbc29 100644
--- a/lib/pure/encodings.nim
+++ b/lib/pure/encodings.nim
@@ -36,7 +36,7 @@ when defined(windows):
     while i < a.len and j < b.len:
       if a[i] in {'-', '_'}: inc i
       if b[j] in {'-', '_'}: inc j
-      if a[i].toLower != b[j].toLower: return false
+      if i < a.len and j < b.len and a[i].toLowerAscii != b[j].toLowerAscii: return false
       inc i
       inc j
     result = i == a.len and j == b.len
diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim
index aeb4a149e..ee2b715d3 100644
--- a/lib/pure/ospaths.nim
+++ b/lib/pure/ospaths.nim
@@ -477,7 +477,7 @@ proc unixToNativePath*(path: string, drive=""): string {.
 
     var i = start
     while i < len(path): # ../../../ --> ::::
-      if path[i] == '.' and path[i+1] == '.' and path[i+2] == '/':
+      if i+2 < path.len and path[i] == '.' and path[i+1] == '.' and path[i+2] == '/':
         # parent directory
         when defined(macos):
           if result[high(result)] == ':':
diff --git a/tests/concepts/tstackconcept.nim b/tests/concepts/tstackconcept.nim
index 2238dacb6..cb8db566d 100644
--- a/tests/concepts/tstackconcept.nim
+++ b/tests/concepts/tstackconcept.nim
@@ -31,7 +31,7 @@ type
     s.pop() is T
 
     type ValueType = T
-    const ValueTypeName = T.name.toUpper
+    const ValueTypeName = T.name.toUpperAscii
 
 proc genericAlgorithm[T](s: var Stack[T], y: T) =
   static:
diff --git a/tests/stdlib/twchartoutf8.nim b/tests/stdlib/twchartoutf8.nim
index b2f68ee32..a6602e3e3 100644
--- a/tests/stdlib/twchartoutf8.nim
+++ b/tests/stdlib/twchartoutf8.nim
@@ -30,7 +30,6 @@ else:
     result = newString(size)
     let res = WideCharToMultiByte(CP_UTF8, 0'i32, cast[LPWCSTR](addr(wc[0])), wclen,
       cstring(result), size, cstring(nil), LPBOOL(nil))
-    result[size] = chr(0)
     doAssert size == res
 
   proc testCP(wc: WideCString, lo, hi: int) =
diff --git a/tests/vm/tcompiletimetable.nim b/tests/vm/tcompiletimetable.nim
index b5b84a790..e78c06536 100644
--- a/tests/vm/tcompiletimetable.nim
+++ b/tests/vm/tcompiletimetable.nim
@@ -4,7 +4,6 @@ discard """
 4:2
 Got Hi
 Got Hey'''
-  disabled: "true"
 """
 
 # bug #404
> 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
#
#
#           The Nim Compiler
#        (c) Copyright 2020 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## New styled concepts for Nim. See https://github.com/nim-lang/RFCs/issues/168
## for details. Note this is a first implementation and only the "Concept matching"
## section has been implemented.

import ast, astalgo, semdata, lookups, lineinfos, idents, msgs, renderer, types

import std/intsets

when defined(nimPreviewSlimSystem):
  import std/assertions

const
  logBindings = false

## Code dealing with Concept declarations
## --------------------------------------

proc declareSelf(c: PContext; info: TLineInfo) =
  ## Adds the magical 'Self' symbols to the current scope.
  let ow = getCurrOwner(c)
  let s = newSym(skType, getIdent(c.cache, "Self"), c.idgen, ow, info)
  s.typ = newType(tyTypeDesc, c.idgen, ow)
  s.typ.flags.incl {tfUnresolved, tfPacked}
  s.typ.add newType(tyEmpty, c.idgen, ow)
  addDecl(c, s, info)

proc semConceptDecl(c: PContext; n: PNode): PNode =
  ## Recursive helper for semantic checking for the concept declaration.
  ## Currently we only support (possibly empty) lists of statements
  ## containing 'proc' declarations and the like.
  case n.kind
  of nkStmtList, nkStmtListExpr:
    result = shallowCopy(n)
    for i in 0..<n.len:
      result[i] = semConceptDecl(c, n[i])
  of nkProcDef..nkIteratorDef, nkFuncDef:
    result = c.semExpr(c, n, {efWantStmt})
  of nkTypeClassTy:
    result = shallowCopy(n)
    for i in 0..<n.len-1:
      result[i] = n[i]
    result[^1] = semConceptDecl(c, n[^1])
  of nkCommentStmt:
    result = n
  else:
    localError(c.config, n.info, "unexpected construct in the new-styled concept: " & renderTree(n))
    result = n

proc semConceptDeclaration*(c: PContext; n: PNode): PNode =
  ## Semantic checking for the concept declaration. Runs
  ## when we process the concept itself, not its matching process.
  assert n.kind == nkTypeClassTy
  inc c.inConceptDecl
  openScope(c)
  declareSelf(c, n.info)
  result = semConceptDecl(c, n)
  rawCloseScope(c)
  dec c.inConceptDecl

## Concept matching
## ----------------

type
  MatchCon = object ## Context we pass around during concept matching.
    inferred: seq[(PType, PType)] ## we need a seq here so that we can easily undo inferences \
      ## that turned out to be wrong.
    marker: IntSet ## Some protection against wild runaway recursions.
    potentialImplementation: PType ## the concrete type that might match the concept we try to match.
    magic: TMagic  ## mArrGet and mArrPut is wrong in system.nim and
                   ## cannot be fixed that easily.
                   ## Thus we special case it here.

proc existingBinding(m: MatchCon; key: PType): PType =
  ## checks if we bound the type variable 'key' already to some
  ## concrete type.
  for i in 0..<m.inferred.len:
    if m.inferred[i][0] == key: return m.inferred[i][1]
  return nil

proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool

proc matchType(c: PContext; f, a: PType; m: var MatchCon): bool =
  ## The heart of the concept matching process. 'f' is the formal parameter of some
  ## routine inside the concept that we're looking for. 'a' is the formal parameter
  ## of a routine that might match.
  const
    ignorableForArgType = {tyVar, tySink, tyLent, tyOwned, tyGenericInst, tyAlias, tyInferred}
  case f.kind
  of tyAlias:
    result = matchType(c, f.skipModifier, a, m)
  of tyTypeDesc:
    if isSelf(f):
      #let oldLen = m.inferred.len
      result = matchType(c, a, m.potentialImplementation, m)
      #echo "self is? ", result, " ", a.kind, " ", a, " ", m.potentialImplementation, " ", m.potentialImplementation.kind
      #m.inferred.setLen oldLen
      #echo "A for ", result, " to ", typeToString(a), " to ", typeToString(m.potentialImplementation)
    else:
      if a.kind == tyTypeDesc and f.hasElementType == a.hasElementType:
        if f.hasElementType:
          result = matchType(c, f.elementType, a.elementType, m)
        else:
          result = true # both lack it
      else:
        result = false

  of tyGenericInvocation:
    result = false
    if a.kind == tyGenericInst and a.genericHead.kind == tyGenericBody:
      if sameType(f.genericHead, a.genericHead) and f.kidsLen == a.kidsLen-1:
        for i in FirstGenericParamAt ..< f.kidsLen:
          if not matchType(c, f[i], a[i], m): return false
        return true
  of tyGenericParam:
    let ak = a.skipTypes({tyVar, tySink, tyLent, tyOwned})
    if ak.kind in {tyTypeDesc, tyStatic} and not isSelf(ak):
      result = false
    else:
      let old = existingBinding(m, f)
      if old == nil:
        if f.hasElementType and f.elementType.kind != tyNone:
          # also check the generic's constraints:
          let oldLen = m.inferred.len
          result = matchType(c, f.elementType, a, m)
          m.inferred.setLen oldLen
          if result:
            when logBindings: echo "A adding ", f, " ", ak
            m.inferred.add((f, ak))
        elif m.magic == mArrGet and ak.kind in {tyArray, tyOpenArray, tySequence, tyVarargs, tyCstring, tyString}:
          when logBindings: echo "B adding ", f, " ", lastSon ak
          m.inferred.add((f, last ak))
          result = true
        else:
          when logBindings: echo "C adding ", f, " ", ak
          m.inferred.add((f, ak))
          #echo "binding ", typeToString(ak), " to ", typeToString(f)
          result = true
      elif not m.marker.containsOrIncl(old.id):
        result = matchType(c, old, ak, m)
        if m.magic == mArrPut and ak.kind == tyGenericParam:
          result = true
      else:
        result = false
    #echo "B for ", result, " to ", typeToString(a), " to ", typeToString(m.potentialImplementation)

  of tyVar, tySink, tyLent, tyOwned:
    # modifiers in the concept must be there in the actual implementation
    # too but not vice versa.
    if a.kind == f.kind:
      result = matchType(c, f.elementType, a.elementType, m)
    elif m.magic == mArrPut:
      result = matchType(c, f.elementType, a, m)
    else:
      result = false
  of tyEnum, tyObject, tyDistinct:
    result = sameType(f, a)
  of tyEmpty, tyString, tyCstring, tyPointer, tyNil, tyUntyped, tyTyped, tyVoid:
    result = a.skipTypes(ignorableForArgType).kind == f.kind
  of tyBool, tyChar, tyInt..tyUInt64:
    let ak = a.skipTypes(ignorableForArgType)
    result = ak.kind == f.kind or ak.kind == tyOrdinal or
       (ak.kind == tyGenericParam and ak.hasElementType and ak.elementType.kind == tyOrdinal)
  of tyConcept:
    let oldLen = m.inferred.len
    let oldPotentialImplementation = m.potentialImplementation
    m.potentialImplementation = a
    result = conceptMatchNode(c, f.n.lastSon, m)
    m.potentialImplementation = oldPotentialImplementation
    if not result:
      m.inferred.setLen oldLen
  of tyArray, tyTuple, tyVarargs, tyOpenArray, tyRange, tySequence, tyRef, tyPtr,
     tyGenericInst:
    # ^ XXX Rewrite this logic, it's more complex than it needs to be.
    result = false
    let ak = a.skipTypes(ignorableForArgType - {f.kind})
    if ak.kind == f.kind and f.kidsLen == ak.kidsLen:
      for i in 0..<ak.kidsLen:
        if not matchType(c, f[i], ak[i], m): return false
      return true
  of tyOr:
    let oldLen = m.inferred.len
    if a.kind == tyOr:
      # say the concept requires 'int|float|string' if the potentialImplementation
      # says 'int|string' that is good enough.
      var covered = 0
      for ff in f.kids:
        for aa in a.kids:
          let oldLenB = m.inferred.len
          let r = matchType(c, ff, aa, m)
          if r:
            inc covered
            break
          m.inferred.setLen oldLenB

      result = covered >= a.kidsLen
      if not result:
        m.inferred.setLen oldLen
    else:
      result = false
      for ff in f.kids:
        result = matchType(c, ff, a, m)
        if result: break # and remember the binding!
        m.inferred.setLen oldLen
  of tyNot:
    if a.kind == tyNot:
      result = matchType(c, f.elementType, a.elementType, m)
    else:
      let oldLen = m.inferred.len
      result = not matchType(c, f.elementType, a, m)
      m.inferred.setLen oldLen
  of tyAnything:
    result = true
  of tyOrdinal:
    result = isOrdinalType(a, allowEnumWithHoles = false) or a.kind == tyGenericParam
  else:
    result = false

proc matchReturnType(c: PContext; f, a: PType; m: var MatchCon): bool =
  ## Like 'matchType' but with extra logic dealing with proc return types
  ## which can be nil or the 'void' type.
  if f.isEmptyType:
    result = a.isEmptyType
  elif a == nil:
    result = false
  else:
    result = matchType(c, f, a, m)

proc matchSym(c: PContext; candidate: PSym, n: PNode; m: var MatchCon): bool =
  ## Checks if 'candidate' matches 'n' from the concept body. 'n' is a nkProcDef
  ## or similar.

  # watch out: only add bindings after a completely successful match.
  let oldLen = m.inferred.len

  let can = candidate.typ.n
  let con = n[0].sym.typ.n

  if can.len < con.len:
    # too few arguments, cannot be a match:
    return false

  let common = min(can.len, con.len)
  for i in 1 ..< common:
    if not matchType(c, con[i].typ, can[i].typ, m):
      m.inferred.setLen oldLen
      return false

  if not matchReturnType(c, n[0].sym.typ.returnType, candidate.typ.returnType, m):
    m.inferred.setLen oldLen
    return false

  # all other parameters have to be optional parameters:
  for i in common ..< can.len:
    assert can[i].kind == nkSym
    if can[i].sym.ast == nil:
      # has too many arguments one of which is not optional:
      m.inferred.setLen oldLen
      return false

  return true

proc matchSyms(c: PContext, n: PNode; kinds: set[TSymKind]; m: var MatchCon): bool =
  ## Walk the current scope, extract candidates which the same name as 'n[namePos]',
  ## 'n' is the nkProcDef or similar from the concept that we try to match.
  let candidates = searchInScopesAllCandidatesFilterBy(c, n[namePos].sym.name, kinds)
  for candidate in candidates:
    #echo "considering ", typeToString(candidate.typ), " ", candidate.magic
    m.magic = candidate.magic
    if matchSym(c, candidate, n, m): return true
  result = false

proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool =
  ## Traverse the concept's AST ('n') and see if every declaration inside 'n'
  ## can be matched with the current scope.
  case n.kind
  of nkStmtList, nkStmtListExpr:
    for i in 0..<n.len:
      if not conceptMatchNode(c, n[i], m):
        return false
    return true
  of nkProcDef, nkFuncDef:
    # procs match any of: proc, template, macro, func, method, converter.
    # The others are more specific.
    # XXX: Enforce .noSideEffect for 'nkFuncDef'? But then what are the use cases...
    const filter = {skProc, skTemplate, skMacro, skFunc, skMethod, skConverter}
    result = matchSyms(c, n, filter, m)
  of nkTemplateDef:
    result = matchSyms(c, n, {skTemplate}, m)
  of nkMacroDef:
    result = matchSyms(c, n, {skMacro}, m)
  of nkConverterDef:
    result = matchSyms(c, n, {skConverter}, m)
  of nkMethodDef:
    result = matchSyms(c, n, {skMethod}, m)
  of nkIteratorDef:
    result = matchSyms(c, n, {skIterator}, m)
  of nkCommentStmt:
    result = true
  else:
    # error was reported earlier.
    result = false

proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var TypeMapping; invocation: PType): bool =
  ## Entry point from sigmatch. 'concpt' is the concept we try to match (here still a PType but
  ## we extract its AST via 'concpt.n.lastSon'). 'arg' is the type that might fulfill the
  ## concept's requirements. If so, we return true and fill the 'bindings' with pairs of
  ## (typeVar, instance) pairs. ('typeVar' is usually simply written as a generic 'T'.)
  ## 'invocation' can be nil for atomic concepts. For non-atomic concepts, it contains the
  ## `C[S, T]` parent type that we look for. We need this because we need to store bindings
  ## for 'S' and 'T' inside 'bindings' on a successful match. It is very important that
  ## we do not add any bindings at all on an unsuccessful match!
  var m = MatchCon(inferred: @[], potentialImplementation: arg)
  result = conceptMatchNode(c, concpt.n.lastSon, m)
  if result:
    for (a, b) in m.inferred:
      if b.kind == tyGenericParam:
        var dest = b
        while true:
          dest = existingBinding(m, dest)
          if dest == nil or dest.kind != tyGenericParam: break
        if dest != nil:
          bindings.idTablePut(a, dest)
          when logBindings: echo "A bind ", a, " ", dest
      else:
        bindings.idTablePut(a, b)
        when logBindings: echo "B bind ", a, " ", b
    # we have a match, so bind 'arg' itself to 'concpt':
    bindings.idTablePut(concpt, arg)
    # invocation != nil means we have a non-atomic concept:
    if invocation != nil and arg.kind == tyGenericInst and invocation.kidsLen == arg.kidsLen-1:
      # bind even more generic parameters
      assert invocation.kind == tyGenericInvocation
      for i in FirstGenericParamAt ..< invocation.kidsLen:
        bindings.idTablePut(invocation[i], arg[i])