summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorZahary Karadjov <zahary@gmail.com>2013-12-09 00:09:03 +0200
committerZahary Karadjov <zahary@gmail.com>2013-12-09 00:09:03 +0200
commitde3b7cd413e02498c0ee5554f55f9c92d2baa9ab (patch)
tree0ce3f72d36fcc09a48b12f7e04a5f7e633da9a18
parent208924b92921ea10afc27dbab059e612113eee48 (diff)
downloadNim-de3b7cd413e02498c0ee5554f55f9c92d2baa9ab.tar.gz
progress towards adding negative type classes
[unittest bugfixes]
the block form of check now allows comments
errors when inspecting the arguments of var-accepting procs
-rw-r--r--compiler/ast.nim11
-rw-r--r--compiler/ccgutils.nim2
-rw-r--r--compiler/jsgen.nim2
-rw-r--r--compiler/semexprs.nim2
-rw-r--r--compiler/seminst.nim5
-rw-r--r--compiler/semtypes.nim9
-rw-r--r--compiler/sigmatch.nim251
-rw-r--r--compiler/types.nim25
-rw-r--r--lib/pure/unittest.nim6
9 files changed, 267 insertions, 46 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 5a5d87d06..1e5276d68 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -335,12 +335,19 @@ type
     tyConst, tyMutable, tyVarargs, 
     tyIter, # unused
     tyProxy # used as errornous type (for idetools)
-    tyTypeClass,
+    tyTypeClass
+    tyAnd
+    tyOr
+    tyNot
+    tyAnything
+    tyParametricTypeClass # structured similarly to tyGenericInst
+                          # lastSon is the body of the type class
 
 const
   tyPureObject* = tyTuple
   GcTypeKinds* = {tyRef, tySequence, tyString}
   tyError* = tyProxy # as an errornous node should match everything
+  tyTypeClasses* = {tyTypeClass, tyParametricTypeClass, tyAnd, tyOr, tyNot, tyAnything}
 
 type
   TTypeKinds* = set[TTypeKind]
@@ -377,6 +384,7 @@ type
                       # used as return types for return type inference)
     tfAll,            # type class requires all constraints to be met (default)
     tfAny,            # type class requires any constraint to be met
+    tfNot,            # type class with a negative check
     tfCapturesEnv,    # whether proc really captures some environment
     tfByCopy,         # pass object/tuple by copy (C backend)
     tfByRef,          # pass object/tuple by reference (C backend)
@@ -1416,3 +1424,4 @@ proc isAtom*(n: PNode): bool {.inline.} =
 proc isEmptyType*(t: PType): bool {.inline.} =
   ## 'void' and 'stmt' types are often equivalent to 'nil' these days:
   result = t == nil or t.kind in {tyEmpty, tyStmt}
+
diff --git a/compiler/ccgutils.nim b/compiler/ccgutils.nim
index c37754511..310f7204a 100644
--- a/compiler/ccgutils.nim
+++ b/compiler/ccgutils.nim
@@ -86,7 +86,7 @@ proc GetUniqueType*(key: PType): PType =
     if result == nil:
       gCanonicalTypes[k] = key
       result = key
-  of tyTypeDesc, tyTypeClass:
+  of tyTypeDesc, tyTypeClasses:
     InternalError("value expected, but got a type")
   of tyGenericParam:
     InternalError("GetUniqueType")
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index 54ee43069..a3c88824d 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -130,7 +130,7 @@ proc mapType(typ: PType): TJSTypeKind =
     result = etyObject
   of tyNil: result = etyNull
   of tyGenericInst, tyGenericParam, tyGenericBody, tyGenericInvokation, tyNone, 
-     tyForward, tyEmpty, tyExpr, tyStmt, tyTypeDesc, tyTypeClass: 
+     tyForward, tyEmpty, tyExpr, tyStmt, tyTypeDesc, tyTypeClasses: 
     result = etyNone
   of tyProc: result = etyProc
   of tyCString: result = etyString
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index 337224aef..5abe8da83 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -322,7 +322,7 @@ proc isOpImpl(c: PContext, n: PNode): PNode =
     var match: bool
     let t2 = n[2].typ
     case t2.kind
-    of tyTypeClass:
+    of tyTypeClasses:
       var m: TCandidate
       InitCandidate(m, t2)
       match = matchUserTypeClass(c, m, emptyNode, t2, t1) != nil
diff --git a/compiler/seminst.nim b/compiler/seminst.nim
index 0cf5086a8..d7d64fd54 100644
--- a/compiler/seminst.nim
+++ b/compiler/seminst.nim
@@ -20,7 +20,8 @@ proc instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable,
     if a.kind != nkSym: 
       InternalError(a.info, "instantiateGenericParamList; no symbol")
     var q = a.sym
-    if q.typ.kind notin {tyTypeDesc, tyGenericParam, tyTypeClass, tyExpr}: continue
+    if q.typ.kind notin {tyTypeDesc, tyGenericParam, tyExpr}+tyTypeClasses:
+      continue
     var s = newSym(skType, q.name, getCurrOwner(), q.info)
     s.flags = s.flags + {sfUsed, sfFromGeneric}
     var t = PType(IdTableGet(pt, q.typ))
@@ -193,7 +194,7 @@ proc fixupProcType(c: PContext, genericType: PType,
   if result == nil: return
 
   case genericType.kind
-  of tyGenericParam, tyTypeClass:
+  of tyGenericParam, tyTypeClasses:
     result = inst.concreteTypes[genericType.sym.position]
   of tyTypeDesc:
     result = inst.concreteTypes[genericType.sym.position]
diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim
index 92f47f585..6c9c476d9 100644
--- a/compiler/semtypes.nim
+++ b/compiler/semtypes.nim
@@ -676,8 +676,13 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
       if lifted != nil:
         paramType.sons[i] = lifted
         result = paramType
-    
-    if result != nil:
+
+    if paramType.lastSon.kind == tyTypeClass:
+      result = paramType
+      result.kind = tyParametricTypeClass
+      result = addImplicitGeneric(copyType(result,
+                                           getCurrOwner(), false))
+    elif result != nil:
       result.kind = tyGenericInvokation
       result.sons.setLen(result.sons.len - 1)
   of tyTypeClass:
diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim
index 12a5708b6..cacf4782e 100644
--- a/compiler/sigmatch.nim
+++ b/compiler/sigmatch.nim
@@ -203,7 +203,7 @@ proc describeArgs*(c: PContext, n: PNode, startIdx = 1): string =
     add(result, argTypeToString(arg))
     if i != sonsLen(n) - 1: add(result, ", ")
 
-proc typeRel*(c: var TCandidate, f, a: PType): TTypeRelation
+proc typeRel*(c: var TCandidate, f, a: PType, doBind = true): TTypeRelation
 proc concreteType(c: TCandidate, t: PType): PType = 
   case t.kind
   of tyArrayConstr: 
@@ -213,7 +213,7 @@ proc concreteType(c: TCandidate, t: PType): PType =
     addSonSkipIntLit(result, t.sons[1]) # XXX: semantic checking for the type?
   of tyNil:
     result = nil              # what should it be?
-  of tyGenericParam: 
+  of tyGenericParam, tyAnything:
     result = t
     while true: 
       result = PType(idTableGet(c.bindings, t))
@@ -385,8 +385,23 @@ proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} =
   else:
     result = isNone
 
-proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation = 
-  # is a subtype of f?
+proc typeRel(c: var TCandidate, f, a: PType, doBind = true): TTypeRelation =
+  # typeRel can be used to establish various relationships between types:
+  #
+  # 1) When used with concrete types, it will check for type equivalence
+  # or a subtype relationship. 
+  #
+  # 2) When used with a concrete type against a type class (such as generic
+  # signature of a proc), it will check whether the concrete type is a member
+  # of the designated type class.
+  #
+  # 3) When used with two type classes, it will check whether the types
+  # matching the first type class are a strict subset of the types matching
+  # the other. This allows us to compare the signatures of generic procs in
+  # order to give preferrence to the most specific one:
+  #
+  # seq[seq[any]] is a strict subset of seq[any] and hence more specific.
+  
   result = isNone
   assert(f != nil)
   assert(a != nil)
@@ -397,6 +412,50 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation =
     return typeRel(c, f, lastSon(a))
   if a.kind == tyVar and f.kind != tyVar:
     return typeRel(c, f, a.sons[0])
+  
+  template bindingRet(res) =
+    when res == isGeneric: put(c.bindings, f, a)
+    return res
+ 
+  case a.kind
+  of tyOr:
+    # seq[int|string] vs seq[number]
+    # both int and string must match against number
+    for branch in a.sons:
+      if typeRel(c, f, branch, false) == isNone:
+        return isNone
+
+    return isGeneric
+
+  of tyAnd:
+    # seq[Sortable and Iterable] vs seq[Sortable]
+    # only one match is enough
+    for branch in a.sons:
+      if typeRel(c, f, branch, false) != isNone:
+        return isGeneric
+
+    return isNone
+
+  of tyNot:
+    case f.kind
+    of tyNot:
+      # seq[!int] vs seq[!number]
+      # seq[float] matches the first, but not the second
+      # we must turn the problem around:
+      # is number a subset of int? 
+      return typeRel(c, a.lastSon, f.lastSon)
+ 
+    else:
+      # negative type classes are essentially infinite,
+      # so only the `any` type class is their superset
+      return if f.kind == tyAnything: isGeneric
+             else: isNone
+
+  of tyAnything:
+    return if f.kind == tyAnything: isGeneric
+           else: isNone
+  else: nil
+
   case f.kind
   of tyEnum: 
     if a.kind == f.kind and sameEnumTypes(f, a): result = isEqual
@@ -485,9 +544,12 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation =
   of tyOrdinal:
     if isOrdinalType(a):
       var x = if a.kind == tyOrdinal: a.sons[0] else: a
-      
-      result = typeRel(c, f.sons[0], x)
-      if result < isGeneric: result = isNone
+     
+      if f.sonsLen == 0:
+        result = isGeneric
+      else:
+        result = typeRel(c, f.sons[0], x)
+        if result < isGeneric: result = isNone
     elif a.kind == tyGenericParam:
       result = isGeneric
   of tyForward: InternalError("forward type in typeRel()")
@@ -574,13 +636,17 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation =
           (a.sons[1].kind == tyChar): 
         result = isConvertible
     else: nil
-  of tyEmpty: 
+
+  of tyEmpty:
     if a.kind == tyEmpty: result = isEqual
-  of tyGenericInst: 
+
+  of tyGenericInst:
     result = typeRel(c, lastSon(f), a)
-  of tyGenericBody: 
+
+  of tyGenericBody:
     let ff = lastSon(f)
     if ff != nil: result = typeRel(c, ff, a)
+
   of tyGenericInvokation:
     var x = a.skipGenericAlias
     if x.kind == tyGenericInvokation or f.sons[0].kind != tyGenericBody:
@@ -604,6 +670,38 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation =
           if x == nil or x.kind in {tyGenericInvokation, tyGenericParam}:
             InternalError("wrong instantiated type!")
           put(c.bindings, f.sons[i], x)
+  
+  of tyAnd:
+    for branch in f.sons:
+      if typeRel(c, branch, a) == isNone:
+        return isNone
+
+    bindingRet isGeneric
+
+  of tyOr:
+    for branch in f.sons:
+      if typeRel(c, branch, a) != isNone:
+        bindingRet isGeneric
+
+    return isNone
+
+  of tyNot:
+    for branch in f.sons:
+      if typeRel(c, branch, a) != isNone:
+        return isNone
+    
+    bindingRet isGeneric
+
+  of tyAnything:
+    var prev = PType(idTableGet(c.bindings, f))
+    if prev == nil:
+      var concrete = concreteType(c, a)
+      if concrete != nil and doBind:
+        put(c.bindings, f, concrete)
+      return isGeneric
+    else:
+      return typeRel(c, prev, a)
+    
   of tyGenericParam, tyTypeClass:
     var x = PType(idTableGet(c.bindings, f))
     if x == nil:
@@ -634,7 +732,7 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation =
         if concrete == nil:
           result = isNone
         else:
-          put(c.bindings, f, concrete)
+          if doBind: put(c.bindings, f, concrete)
     elif a.kind == tyEmpty:
       result = isGeneric
     elif x.kind == tyGenericParam:
@@ -809,8 +907,8 @@ proc ParamTypesMatchAux(c: PContext, m: var TCandidate, f, argType: PType,
         InternalAssert a.len > 0
         r = typeRel(m, f.lastSon, a.lastSon)
       else:
-        let match = matchTypeClass(m, fMaybeExpr, a)
-        if match != isGeneric: r = isNone
+        let match = matchTypeClass(m.bindings, fMaybeExpr, a)
+        if not match: r = isNone
         else:
           # XXX: Ideally, this should happen much earlier somewhere near 
           # semOpAux, but to do that, we need to be able to query the 
@@ -827,7 +925,7 @@ proc ParamTypesMatchAux(c: PContext, m: var TCandidate, f, argType: PType,
         
     if r == isGeneric:
       put(m.bindings, f, arg.typ)
-  of tyTypeClass:
+  of tyTypeClass, tyParametricTypeClass:
     if fMaybeExpr.n != nil:
       let match = matchUserTypeClass(c, m, arg, fMaybeExpr, a)
       if match != nil:
@@ -1156,15 +1254,120 @@ proc argtypeMatches*(c: PContext, f, a: PType): bool =
 
 include suggest
 
+tests:
+  var dummyOwner = newSym(skModule, getIdent("test_module"), nil, UnknownLineInfo())
+  
+  proc `|` (t1, t2: PType): PType =
+    result = newType(tyOr, dummyOwner)
+    result.rawAddSon(t1)
+    result.rawAddSon(t2)
+
+  proc `&` (t1, t2: PType): PType =
+    result = newType(tyAnd, dummyOwner)
+    result.rawAddSon(t1)
+    result.rawAddSon(t2)
+
+  proc `!` (t: PType): PType =
+    result = newType(tyNot, dummyOwner)
+    result.rawAddSon(t)
+
+  proc seq(t: PType): PType =
+    result = newType(tySequence, dummyOwner)
+    result.rawAddSon(t)
+
+  proc array(x: int, t: PType): PType =
+    result = newType(tyArray, dummyOwner)
+    
+    var n = newNodeI(nkRange, UnknownLineInfo())
+    addSon(n, newIntNode(nkIntLit, 0))
+    addSon(n, newIntNode(nkIntLit, x))
+    let range = newType(tyRange, dummyOwner)
+    
+    result.rawAddSon(range)
+    result.rawAddSon(t)
+
+  suite "type classes":
+    let
+      int = newType(tyInt, dummyOwner)
+      float = newType(tyFloat, dummyOwner)
+      string = newType(tyString, dummyOwner)
+      ordinal = newType(tyOrdinal, dummyOwner)
+      any = newType(tyAnything, dummyOwner)
+      number = int | float
+
+    var TFoo = newType(tyObject, dummyOwner)
+    TFoo.sym = newSym(skType, getIdent"TFoo", dummyOwner, UnknownLineInfo())
+
+    var T1 = newType(tyGenericParam, dummyOwner)
+    T1.sym = newSym(skType, getIdent"T1", dummyOwner, UnknownLineInfo())
+    T1.sym.position = 0
+
+    var T2 = newType(tyGenericParam, dummyOwner)
+    T2.sym = newSym(skType, getIdent"T2", dummyOwner, UnknownLineInfo())
+    T2.sym.position = 1
+
+    setup:
+      var c: TCandidate
+      InitCandidate(c, nil)
+
+    template yes(x, y) =
+      test astToStr(x) & " is " & astToStr(y):
+        check typeRel(c, y, x) == isGeneric
+
+    template no(x, y) =
+      test astToStr(x) & " is not " & astToStr(y):
+        check typeRel(c, y, x) == isNone
+    
+    yes seq(any), array(10, int) | seq(any)
+    # Sure, seq[any] is directly included
 
+    yes seq(int), seq(any)
+    yes seq(int), seq(number)
+    # Sure, the int sequence is certainly
+    # part of the number sequences (and all sequences)
+    
+    no seq(any), seq(float)
+    # Nope, seq[any] includes types that are not seq[float] (e.g. seq[int])
+
+    yes seq(int|string), seq(any)
+    # Sure
+ 
+    yes seq(int&string), seq(any)
+    # Again
+    
+    yes seq(int&string), seq(int)
+    # A bit more complicated
+    # seq[int&string] is not a real type, but it's analogous to
+    # seq[Sortable and Iterable], which is certainly a subset of seq[Sortable]
+
+    no seq(int|string), seq(int|float)
+    # Nope, seq[string] is not included in not included in
+    # the seq[int|float] set
+    
+    no seq(!(int|string)), seq(string)
+    # A sequence that is neither seq[int] or seq[string]
+    # is obviously not seq[string]
+     
+    no seq(!int), seq(number)
+    # Now your head should start to hurt a bit
+    # A sequence that is not seq[int] is not necessarily a number sequence
+    # it could well be seq[string] for example
+    
+    yes seq(!(int|string)), seq(!string)
+    # all sequnece types besides seq[int] and seq[string]
+    # are subset of all sequence types that are not seq[string]
+
+    no seq(!(int|string)), seq(!(string|TFoo))
+    # Nope, seq[TFoo] is included in the first set, but not in the second
+    
+    no seq(!string), seq(!number)
+    # Nope, seq[int] in included in the first set, but not in the second
+
+    yes seq(!number), seq(any)
+    yes seq(!int), seq(any)
+    no seq(any), seq(!any)
+    no seq(!int), seq(!any)
+    
+    yes int, ordinal
+    no  string, ordinal
 
-tests:
-  suite "typerel":
-    test "ordinals":
-      # var owner = newSym(skModule, getIdent("dummy"), nil, UnknownLineInfo())
-      var m: TCandidate
-      InitCandidate(m, f)
-
-      # let f = newType(tyOrdinal, owner)
-      # let a = getSysType(tyInt)
-      # check typerel(m, f, a) == isGeneric
diff --git a/compiler/types.nim b/compiler/types.nim
index 4dec9ea2f..7e07a0667 100644
--- a/compiler/types.nim
+++ b/compiler/types.nim
@@ -382,33 +382,34 @@ proc mutateTypeAux(marker: var TIntSet, t: PType, iter: TTypeMutator,
     if t.n != nil: result.n = mutateNode(marker, t.n, iter, closure)
   assert(result != nil)
 
-proc mutateType(t: PType, iter: TTypeMutator, closure: PObject): PType = 
+proc mutateType(t: PType, iter: TTypeMutator, closure: PObject): PType =
   var marker = InitIntSet()
   result = mutateTypeAux(marker, t, iter, closure)
 
-proc ValueToString(a: PNode): string = 
+proc ValueToString(a: PNode): string =
   case a.kind
   of nkCharLit..nkUInt64Lit: result = $(a.intVal)
   of nkFloatLit..nkFloat128Lit: result = $(a.floatVal)
   of nkStrLit..nkTripleStrLit: result = a.strVal
   else: result = "<invalid value>"
 
-proc rangeToStr(n: PNode): string = 
+proc rangeToStr(n: PNode): string =
   assert(n.kind == nkRange)
   result = ValueToString(n.sons[0]) & ".." & ValueToString(n.sons[1])
 
 const 
-  typeToStr: array[TTypeKind, string] = ["None", "bool", "Char", "empty", 
-    "Array Constructor [$1]", "nil", "expr", "stmt", "typeDesc", 
-    "GenericInvokation", "GenericBody", "GenericInst", "GenericParam", 
-    "distinct $1", "enum", "ordinal[$1]", "array[$1, $2]", "object", "tuple", 
-    "set[$1]", "range[$1]", "ptr ", "ref ", "var ", "seq[$1]", "proc", 
+  typeToStr: array[TTypeKind, string] = ["None", "bool", "Char", "empty",
+    "Array Constructor [$1]", "nil", "expr", "stmt", "typeDesc",
+    "GenericInvokation", "GenericBody", "GenericInst", "GenericParam",
+    "distinct $1", "enum", "ordinal[$1]", "array[$1, $2]", "object", "tuple",
+    "set[$1]", "range[$1]", "ptr ", "ref ", "var ", "seq[$1]", "proc",
     "pointer", "OpenArray[$1]", "string", "CString", "Forward",
     "int", "int8", "int16", "int32", "int64",
     "float", "float32", "float64", "float128",
     "uint", "uint8", "uint16", "uint32", "uint64",
     "bignum", "const ",
-    "!", "varargs[$1]", "iter[$1]", "Error Type", "TypeClass"]
+    "!", "varargs[$1]", "iter[$1]", "Error Type", "TypeClass",
+    "ParametricTypeClass", "and", "or", "not", "any"]
 
 proc consToStr(t: PType): string =
   if t.len > 0: result = t.typeToString
@@ -421,7 +422,7 @@ proc constraintsToStr(t: PType): string =
     if i > 0: result.add(sep)
     result.add(t.sons[i].consToStr)
 
-proc TypeToString(typ: PType, prefer: TPreferedDesc = preferName): string = 
+proc TypeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
   var t = typ
   result = ""
   if t == nil: return 
@@ -861,7 +862,7 @@ proc SameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
   of tyGenericParam, tyGenericInvokation, tyGenericBody, tySequence,
      tyOpenArray, tySet, tyRef, tyPtr, tyVar, tyArrayConstr,
      tyArray, tyProc, tyConst, tyMutable, tyVarargs, tyIter,
-     tyOrdinal, tyTypeClass:
+     tyOrdinal, tyTypeClasses:
     CycleCheck()    
     result = sameChildrenAux(a, b, c) and sameFlags(a, b)
     if result and (a.kind == tyProc):
@@ -1042,7 +1043,7 @@ proc typeAllowedAux(marker: var TIntSet, typ: PType, kind: TSymKind,
     # XXX er ... no? these should not be allowed!
   of tyEmpty:
     result = taField in flags
-  of tyTypeClass:
+  of tyTypeClasses:
     result = true
   of tyGenericBody, tyGenericParam, tyForward, tyNone, tyGenericInvokation:
     result = false
diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim
index 20f5c610e..f847d24f4 100644
--- a/lib/pure/unittest.nim
+++ b/lib/pure/unittest.nim
@@ -115,7 +115,8 @@ macro check*(conditions: stmt): stmt {.immediate.} =
     counter = 0
 
   template asgn(a, value: expr): stmt =
-    let a = value
+    var a = value # XXX: we need "var: var" here in order to
+                  # preserve the semantics of var params
   
   template print(name, value: expr): stmt =
     when compiles(string($value)):
@@ -150,7 +151,8 @@ macro check*(conditions: stmt): stmt {.immediate.} =
   of nnkStmtList:
     result = newNimNode(nnkStmtList)
     for i in countup(0, checked.len - 1):
-      result.add(newCall(!"check", checked[i]))
+      if checked[i].kind != nnkCommentStmt:
+        result.add(newCall(!"check", checked[i]))
 
   else:
     template rewrite(Exp, lineInfoLit: expr, expLit: string): stmt =