From 66e0f0e19cc47caa01aa898988eb7452d07e22cf Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 29 Jul 2016 04:56:14 +0300 Subject: [PATCH 01/37] concept type params inference working in basic examples --- compiler/ast.nim | 1 + compiler/semexprs.nim | 4 ++- compiler/semtypes.nim | 3 +- compiler/sigmatch.nim | 38 +++++++++++++++++++++++++ tests/concepts/tstackconcept.nim | 49 ++++++++++++++++++++++++++++++++ 5 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 tests/concepts/tstackconcept.nim diff --git a/compiler/ast.nim b/compiler/ast.nim index 26305cf3b..4a25e0336 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -482,6 +482,7 @@ type tfHasStatic tfGenericTypeParam tfImplicitTypeParam + tfInferrableTypeClassTypeParam tfWildcard # consider a proc like foo[T, I](x: Type[T, I]) # T and I here can bind to both typedesc and static types # before this is determined, we'll consider them to be a diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 4d698dbfc..cb16bf406 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -318,7 +318,9 @@ proc isOpImpl(c: PContext, n: PNode): PNode = else: result = newIntNode(nkIntLit, 0) else: - var t2 = n[2].typ.skipTypes({tyTypeDesc}) + var rhsOrigType = n[2].typ + discard inferTypeClassParam(c, t1, rhsOrigType) + var t2 = rhsOrigType.skipTypes({tyTypeDesc}) maybeLiftType(t2, c, n.info) var m: TCandidate initCandidate(c, m, t2) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index e86b527d6..e3c3123f9 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -883,7 +883,8 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, for i in 1 .. 0 and t.kind == tyTypeDesc: + t = t.base + else: + return nil + + return nil + +proc inferTypeClassParam*(c: PContext, f, a: PType): bool = + if c.inTypeClass == 0: return false + + var inferrableType = a.skipToInferrableParam + if inferrableType == nil: return false + + inferrableType.assignType f + return true + proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, argSemantized, argOrig: PNode): PNode = var @@ -1363,6 +1398,9 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, argType = argType c = m.c + if inferTypeClassParam(c, f, argType): + return argSemantized + if tfHasStatic in fMaybeStatic.flags: # XXX: When implicit statics are the default # this will be done earlier - we just have to diff --git a/tests/concepts/tstackconcept.nim b/tests/concepts/tstackconcept.nim new file mode 100644 index 000000000..3993ca534 --- /dev/null +++ b/tests/concepts/tstackconcept.nim @@ -0,0 +1,49 @@ +discard """ +output: "20\n10" +msg: ''' +INFERRED int +''' +""" + +import typetraits + +template reject(e: expr) = + static: assert(not compiles(e)) + +type + ArrayStack = object + data: seq[int] + +proc push(s: var ArrayStack, item: int) = + s.data.add item + +proc pop(s: var ArrayStack): int = + return s.data.pop() + +type + Stack[T] = concept var s + s.push(T) + s.pop() is T + +proc genericAlgorithm[T](s: var Stack[T], y: T) = + static: echo "INFERRED ", T.name + + s.push(y) + echo s.pop + +proc implicitGeneric(s: var Stack): auto = + # static: echo "IMPLICIT INFERRED ", s.T.name, " ", Stack.T.name + + return s.pop() + +var s = ArrayStack(data: @[]) + +s.push 10 +s.genericAlgorithm 20 +echo s.implicitGeneric + +reject s.genericAlgorithm "x" +reject s.genericAlgorithm 1.0 +reject "str".implicitGeneric +reject implicitGeneric(10) + From eab1d0cc02beaa2f749725e2c3d8f19bb792a24c Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 29 Jul 2016 18:12:05 +0300 Subject: [PATCH 02/37] support for accessing the inferred concept type params with the dot operator --- compiler/ccgutils.nim | 2 ++ compiler/semexprs.nim | 4 ++-- compiler/semtypinst.nim | 2 +- compiler/sigmatch.nim | 17 ++++++++++++++--- compiler/types.nim | 3 +++ tests/concepts/tstackconcept.nim | 3 ++- 6 files changed, 24 insertions(+), 7 deletions(-) diff --git a/compiler/ccgutils.nim b/compiler/ccgutils.nim index ff8f768bd..afcf24167 100644 --- a/compiler/ccgutils.nim +++ b/compiler/ccgutils.nim @@ -101,6 +101,8 @@ proc getUniqueType*(key: PType): PType = gCanonicalTypes[k] = key result = key of tyTypeDesc, tyTypeClasses, tyGenericParam, tyFromExpr, tyFieldAccessor: + if key.isResolvedUserTypeClass: + return getUniqueType(lastSon(key)) if key.sym != nil: internalError(key.sym.info, "metatype not eliminated") else: diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index cb16bf406..a14bc6c79 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -909,12 +909,12 @@ proc makeDeref(n: PNode): PNode = t = skipTypes(baseTyp, {tyGenericInst, tyAlias}) const - tyTypeParamsHolders = {tyGenericInst, tyCompositeTypeClass} + tyTypeParamsHolders = {tyGenericInst, tyUserTypeClassInst, tyCompositeTypeClass} tyDotOpTransparent = {tyVar, tyPtr, tyRef, tyAlias} proc readTypeParameter(c: PContext, typ: PType, paramName: PIdent, info: TLineInfo): PNode = - let ty = if typ.kind == tyGenericInst: typ.skipGenericAlias + let ty = if typ.kind in {tyGenericInst, tyUserTypeClassInst}: typ.skipGenericAlias else: (internalAssert(typ.kind == tyCompositeTypeClass); typ.sons[1].skipGenericAlias) let tbody = ty.sons[0] diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 75dffb67f..9a1ace42e 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -447,7 +447,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = of tyUserTypeClass: result = t - of tyGenericInst: + of tyGenericInst, tyUserTypeClassInst: bailout() result = instCopyType(cl, t) idTablePut(cl.localCache, t, result) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index b279e7d2d..f8004fa21 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -642,7 +642,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, # We need to put them in the current sigmatch's binding table in order for them # to be resolvable while matching the rest of the parameters for p in typeParams: - put(m.bindings, p[0].typ, p[1]) + put(m.bindings, p[1], p[0].typ) return isGeneric @@ -716,6 +716,9 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = tyGenericInst, tyGenericParam} + tyTypeClasses: return typeRel(c, f, lastSon(a)) + if a.isResolvedUserTypeClass: + return typeRel(c, f, a.lastSon) + template bindingRet(res) = if doBind: let bound = aOrig.skipTypes({tyRange}).skipIntLit @@ -1125,12 +1128,20 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = else: return isNone - of tyUserTypeClass, tyUserTypeClassInst: + of tyUserTypeClass: considerPreviousT: result = matchUserTypeClass(c.c, c, f, aOrig) if result == isGeneric: put(c, f, a) + of tyUserTypeClassInst: + considerPreviousT: + result = matchUserTypeClass(c.c, c, f, aOrig) + if result == isGeneric: + var fWithResolvedParams = generateTypeInstance(c.c, c.bindings, c.call.info, f) + fWithResolvedParams.sons.add a + put(c.bindings, f, fWithResolvedParams) + of tyCompositeTypeClass: considerPreviousT: let roota = a.skipGenericAlias @@ -1397,7 +1408,7 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, arg = argSemantized argType = argType c = m.c - + if inferTypeClassParam(c, f, argType): return argSemantized diff --git a/compiler/types.nim b/compiler/types.nim index f4ef75094..285854aa0 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -1485,6 +1485,9 @@ proc isEmptyContainer*(t: PType): bool = of tyGenericInst, tyAlias: result = isEmptyContainer(t.lastSon) else: result = false +proc isResolvedUserTypeClass*(t: PType): bool = + t.kind in {tyUserTypeClassInst} and t.base.sonsLen == t.sonsLen - 2 + proc takeType*(formal, arg: PType): PType = # param: openArray[string] = [] # [] is an array constructor of length 0 of type string! diff --git a/tests/concepts/tstackconcept.nim b/tests/concepts/tstackconcept.nim index 3993ca534..dc75df5ff 100644 --- a/tests/concepts/tstackconcept.nim +++ b/tests/concepts/tstackconcept.nim @@ -2,6 +2,7 @@ discard """ output: "20\n10" msg: ''' INFERRED int +IMPLICIT INFERRED int int ''' """ @@ -32,7 +33,7 @@ proc genericAlgorithm[T](s: var Stack[T], y: T) = echo s.pop proc implicitGeneric(s: var Stack): auto = - # static: echo "IMPLICIT INFERRED ", s.T.name, " ", Stack.T.name + static: echo "IMPLICIT INFERRED ", s.T.name, " ", Stack.T.name return s.pop() From 33f4e69759820021f6b5089fe3798233b2a1fb13 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Sat, 30 Jul 2016 13:21:14 +0300 Subject: [PATCH 03/37] support for accessing types and consts defined in concepts with the dot operator --- compiler/semexprs.nim | 27 +++++++++++++++++++++++- compiler/sigmatch.nim | 35 +++++++++++++++++++++----------- tests/concepts/tstackconcept.nim | 19 ++++++++++++++--- 3 files changed, 65 insertions(+), 16 deletions(-) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index a14bc6c79..b3fe83ed8 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -912,11 +912,14 @@ const tyTypeParamsHolders = {tyGenericInst, tyUserTypeClassInst, tyCompositeTypeClass} tyDotOpTransparent = {tyVar, tyPtr, tyRef, tyAlias} + + proc readTypeParameter(c: PContext, typ: PType, paramName: PIdent, info: TLineInfo): PNode = let ty = if typ.kind in {tyGenericInst, tyUserTypeClassInst}: typ.skipGenericAlias else: (internalAssert(typ.kind == tyCompositeTypeClass); typ.sons[1].skipGenericAlias) + let tbody = ty.sons[0] for s in countup(0, tbody.len-2): let tParam = tbody.sons[s] @@ -927,7 +930,29 @@ proc readTypeParameter(c: PContext, typ: PType, else: let foundTyp = makeTypeDesc(c, rawTyp) return newSymNode(copySym(tParam.sym).linkTo(foundTyp), info) - #echo "came here: returned nil" + + if ty.n != nil: + for statement in ty.n: + case statement.kind + of nkTypeSection: + for def in statement: + if def[0].sym.name.id == paramName.id: + # XXX: Instead of lifting the section type to a typedesc + # here, we could try doing it earlier in semTypeSection. + # This seems semantically correct and then we'll be able + # to return the section symbol directly here + let foundType = makeTypeDesc(c, def[2].typ) + return newSymNode(copySym(def[2].sym).linkTo(foundType), info) + + of nkConstSection: + for def in statement: + if def[0].sym.name.id == paramName.id: + return def[2] + + else: + discard + + return nil proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = let s = getGenSym(c, sym) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index f8004fa21..f819af13d 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -572,11 +572,11 @@ proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} = result = isNone proc matchUserTypeClass*(c: PContext, m: var TCandidate, - ff, a: PType): TTypeRelation = + ff, a: PType): PType = var body = ff.skipTypes({tyUserTypeClassInst}) if c.inTypeClass > 4: localError(body.n[3].info, $body.n[3] & " too nested for type matching") - return isNone + return nil openScope(c) inc c.inTypeClass @@ -636,7 +636,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, #echo "B ", dummyName.ident.s, " ", typeToString(dummyType), " ", dummyparam.kind var checkedBody = c.semTryExpr(c, body.n[3].copyTree) - if checkedBody == nil: return isNone + if checkedBody == nil: return nil # The inferrable type params have been identified during the semTryExpr above. # We need to put them in the current sigmatch's binding table in order for them @@ -644,7 +644,12 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, for p in typeParams: put(m.bindings, p[1], p[0].typ) - return isGeneric + if ff.kind == tyUserTypeClassInst: + result = generateTypeInstance(c, m.bindings, m.call.info, ff) + else: + result = copyType(ff, ff.owner, true) + + result.n = checkedBody proc shouldSkipDistinct(rules: PNode, callIdent: PIdent): bool = if rules.kind == nkWith: @@ -1130,17 +1135,23 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = of tyUserTypeClass: considerPreviousT: - result = matchUserTypeClass(c.c, c, f, aOrig) - if result == isGeneric: + var matched = matchUserTypeClass(c.c, c, f, aOrig) + if matched != nil: + # TODO, make user type classes skipable too put(c, f, a) + result = isGeneric + else: + result = isNone of tyUserTypeClassInst: considerPreviousT: - result = matchUserTypeClass(c.c, c, f, aOrig) - if result == isGeneric: - var fWithResolvedParams = generateTypeInstance(c.c, c.bindings, c.call.info, f) - fWithResolvedParams.sons.add a - put(c.bindings, f, fWithResolvedParams) + var matched = matchUserTypeClass(c.c, c, f, aOrig) + if matched != nil: + matched.sons.add a + put(c.bindings, f, matched) + result = isGeneric + else: + result = isNone of tyCompositeTypeClass: considerPreviousT: @@ -1408,7 +1419,7 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, arg = argSemantized argType = argType c = m.c - + if inferTypeClassParam(c, f, argType): return argSemantized diff --git a/tests/concepts/tstackconcept.nim b/tests/concepts/tstackconcept.nim index dc75df5ff..11d6505cf 100644 --- a/tests/concepts/tstackconcept.nim +++ b/tests/concepts/tstackconcept.nim @@ -2,11 +2,15 @@ discard """ output: "20\n10" msg: ''' INFERRED int +VALUE TYPE int +VALUE TYPE NAME INT IMPLICIT INFERRED int int +IMPLICIT VALUE TYPE int int +IMPLICIT VALUE TYPE NAME INT INT ''' """ -import typetraits +import typetraits, strutils template reject(e: expr) = static: assert(not compiles(e)) @@ -26,14 +30,23 @@ type s.push(T) s.pop() is T + type ValueType = T + const ValueTypeName = T.name.toUpper + proc genericAlgorithm[T](s: var Stack[T], y: T) = - static: echo "INFERRED ", T.name + static: + echo "INFERRED ", T.name + echo "VALUE TYPE ", s.ValueType.name + echo "VALUE TYPE NAME ", s.ValueTypeName s.push(y) echo s.pop proc implicitGeneric(s: var Stack): auto = - static: echo "IMPLICIT INFERRED ", s.T.name, " ", Stack.T.name + static: + echo "IMPLICIT INFERRED ", s.T.name, " ", Stack.T.name + echo "IMPLICIT VALUE TYPE ", s.ValueType.name, " ", Stack.ValueType.name + echo "IMPLICIT VALUE TYPE NAME ", s.ValueTypeName, " ", s.ValueTypeName return s.pop() From 0a9a878bd34c9e38eeee36180ce171f54423b45c Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Sun, 31 Jul 2016 02:13:32 +0300 Subject: [PATCH 04/37] support for matching generic types' procs in generic concepts --- compiler/semexprs.nim | 4 +- compiler/semstmts.nim | 6 +- compiler/sigmatch.nim | 42 +++++++++++-- tests/concepts/tmapconcept.nim | 102 +++++++++++++++++++++++++++++++ tests/concepts/tstackconcept.nim | 2 +- 5 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 tests/concepts/tmapconcept.nim diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index b3fe83ed8..d81abe20e 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -319,11 +319,11 @@ proc isOpImpl(c: PContext, n: PNode): PNode = result = newIntNode(nkIntLit, 0) else: var rhsOrigType = n[2].typ - discard inferTypeClassParam(c, t1, rhsOrigType) var t2 = rhsOrigType.skipTypes({tyTypeDesc}) maybeLiftType(t2, c, n.info) var m: TCandidate initCandidate(c, m, t2) + discard inferTypeClassParam(m, t1, rhsOrigType) let match = typeRel(m, t2, t1) >= isSubtype # isNone result = newIntNode(nkIntLit, ord(match)) @@ -912,8 +912,6 @@ const tyTypeParamsHolders = {tyGenericInst, tyUserTypeClassInst, tyCompositeTypeClass} tyDotOpTransparent = {tyVar, tyPtr, tyRef, tyAlias} - - proc readTypeParameter(c: PContext, typ: PType, paramName: PIdent, info: TLineInfo): PNode = let ty = if typ.kind in {tyGenericInst, tyUserTypeClassInst}: typ.skipGenericAlias diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 9a1850932..33b684e91 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1631,8 +1631,12 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = of nkPragma, nkCommentStmt, nkNilLit, nkEmpty: discard else: localError(n.sons[j].info, errStmtInvalidAfterReturn) else: discard - if result.len == 1 and result.sons[0].kind != nkDefer: + + if result.len == 1 and + c.inTypeClass == 0 and # concept bodies should be preserved as a stmt list + result.sons[0].kind != nkDefer: result = result.sons[0] + when defined(nimfix): if result.kind == nkCommentStmt and not result.comment.isNil and not (result.comment[0] == '#' and result.comment[1] == '#'): diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index f819af13d..3dede8b6f 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -645,7 +645,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, put(m.bindings, p[1], p[0].typ) if ff.kind == tyUserTypeClassInst: - result = generateTypeInstance(c, m.bindings, m.call.info, ff) + result = generateTypeInstance(c, m.bindings, ff.sym.info, ff) else: result = copyType(ff, ff.owner, true) @@ -1403,13 +1403,34 @@ proc skipToInferrableParam(tt: PType): PType = return nil -proc inferTypeClassParam*(c: PContext, f, a: PType): bool = +proc inferTypeClassParam*(m: var TCandidate, f, a: PType): bool = + var c = m.c if c.inTypeClass == 0: return false var inferrableType = a.skipToInferrableParam if inferrableType == nil: return false - inferrableType.assignType f + var inferAs = f + + case f.kind + of tyGenericParam: + var prev = PType(idTableGet(m.bindings, f)) + if prev != nil: inferAs = prev + + of tyFromExpr: + let computedType = tryResolvingStaticExpr(m, f.n).typ + case computedType.kind + of tyTypeDesc: + inferAs = computedType.base + of tyStatic: + inferAs = computedType + else: + localError(f.n.info, errTypeExpected) + + else: + discard + + inferrableType.assignType inferAs return true proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, @@ -1420,7 +1441,7 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, argType = argType c = m.c - if inferTypeClassParam(c, f, argType): + if inferTypeClassParam(m, f, argType): return argSemantized if tfHasStatic in fMaybeStatic.flags: @@ -1448,9 +1469,18 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, arg.typ.n = evaluated argType = arg.typ - var a = argType - var r = typeRel(m, f, a) + var + useTypeLoweringRuleInTypeClass = c.inTypeClass > 0 and + not m.isNoCall and + f.kind != tyTypeDesc + a = if useTypeLoweringRuleInTypeClass: + argType.skipTypes({tyTypeDesc, tyFieldAccessor}) + else: + argType + + r = typeRel(m, f, a) + if r != isNone and m.calleeSym != nil and m.calleeSym.kind in {skMacro, skTemplate}: # XXX: duplicating this is ugly, but we cannot (!) move this diff --git a/tests/concepts/tmapconcept.nim b/tests/concepts/tmapconcept.nim new file mode 100644 index 000000000..81caed7c6 --- /dev/null +++ b/tests/concepts/tmapconcept.nim @@ -0,0 +1,102 @@ +discard """ +output: '''10 +10 +nil +1''' +msg: ''' +K=string V=int +K=int64 V=string +K=int V=int +''' +""" + +import tables, typetraits + +template ok(check) = assert check +template no(check) = assert(not check) + +type + Enumerable[T] = concept e + for v in e: + v is T + + Map[K, V] = concept m, var mvar + m[K] is V + mvar[K] = V + m.contains(K) is bool + m.valuesSeq is Enumerable[V] + + TreeMap[K, V] = object + root: int + + SparseSeq = object + data: seq[int] + + JudyArray = object + data: SparseSeq + +static: + ok seq[int] is Enumerable[int] + ok seq[string] is Enumerable + ok seq[int] is Enumerable[SomeNumber] + ok SparseSeq.data is Enumerable + no seq[string] is Enumerable[int] + no int is Enumerable + no int is Enumerable[int] + +# Complete the map concept implementation for the Table type +proc valuesSeq[K, V](t: Table[K, V]): seq[V] = + result = @[] + for k, v in t: + result.add v + +# Map concept inplementation for TreeMap +proc valuesSeq(t: TreeMap): array[1, TreeMap.V] = + var v: t.V + result = [v] + +proc contains[K, V](t: TreeMap[K, V], key: K): bool = true + +proc `[]=`[K, V](t: var TreeMap[K, V], key: K, val: V) = discard +proc `[]`(t: TreeMap, key: t.K): TreeMap.V = discard + +# Map concept implementation for the non-generic JudyArray +proc valuesSeq(j: JudyArray): SparseSeq = j.data + +proc contains(t: JudyArray, key: int): bool = true + +proc `[]=`(t: var JudyArray, key, val: int) = discard +proc `[]`(t: JudyArray, key: int): int = discard + +iterator items(s: SparseSeq): int = + for i in s.data: yield i + +# Generic proc defined over map +proc getFirstValue[K,V](m : Map[K,V]): V = + static: echo "K=", K.name, " V=", V.name + + for i in m.valuesSeq: + return i + + raise newException(RangeError, "no values") + +proc useConceptProcInGeneric[K, V](t: Table[K, V]): V = + return t.getFirstValue + +var t = initTable[string, int]() +t["test"] = 10 + +echo t.getFirstValue +echo t.useConceptProcInGeneric + +var tm = TreeMap[int64, string](root: 0) +echo getFirstValue(tm) + +var j = JudyArray(data: SparseSeq(data: @[1, 2, 3])) +echo getFirstValue(j) + +static: + ok Table[int, float] is Map + ok Table[int, string] is Map[SomeNumber, string] + no JudyArray is Map[string, int] + diff --git a/tests/concepts/tstackconcept.nim b/tests/concepts/tstackconcept.nim index 11d6505cf..b6ead2c2b 100644 --- a/tests/concepts/tstackconcept.nim +++ b/tests/concepts/tstackconcept.nim @@ -46,7 +46,7 @@ proc implicitGeneric(s: var Stack): auto = static: echo "IMPLICIT INFERRED ", s.T.name, " ", Stack.T.name echo "IMPLICIT VALUE TYPE ", s.ValueType.name, " ", Stack.ValueType.name - echo "IMPLICIT VALUE TYPE NAME ", s.ValueTypeName, " ", s.ValueTypeName + echo "IMPLICIT VALUE TYPE NAME ", s.ValueTypeName, " ", Stack.ValueTypeName return s.pop() From 815724db713fb0f6f06570b9e85ce2434e7c45b8 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Sun, 31 Jul 2016 21:32:26 +0300 Subject: [PATCH 05/37] support the full range of type modifiers when declaring concept vars and testing proc signatures --- compiler/parampatterns.nim | 7 +++ compiler/parser.nim | 12 ++++- compiler/semdata.nim | 18 +++++++- compiler/semtypes.nim | 6 +-- compiler/sigmatch.nim | 31 ++++++++----- tests/concepts/tusertypeclasses.nim | 70 ++++++++++++++++++----------- 6 files changed, 100 insertions(+), 44 deletions(-) diff --git a/compiler/parampatterns.nim b/compiler/parampatterns.nim index c51d406ac..05b2d8f9c 100644 --- a/compiler/parampatterns.nim +++ b/compiler/parampatterns.nim @@ -235,6 +235,13 @@ proc isAssignable*(owner: PSym, n: PNode; isUnsafeAddr=false): TAssignableResult of nkStmtList, nkStmtListExpr: if n.typ != nil: result = isAssignable(owner, n.lastSon, isUnsafeAddr) + 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)" + # within concepts. Semantically, it's not correct to say that + # nkVarTy denotes an lvalue, but the example above is the only + # possible code which will get us here + result = arLValue else: discard diff --git a/compiler/parser.nim b/compiler/parser.nim index 362a5c286..b63bab781 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -1793,8 +1793,16 @@ proc parseObject(p: var TParser): PNode = addSon(result, parseObjectPart(p)) proc parseTypeClassParam(p: var TParser): PNode = - if p.tok.tokType in {tkOut, tkVar}: - result = newNodeP(nkVarTy, p) + let modifier = case p.tok.tokType + of tkOut, tkVar: nkVarTy + of tkPtr: nkPtrTy + of tkRef: nkRefTy + of tkStatic: nkStaticTy + of tkType: nkTypeOfExpr + else: nkEmpty + + if modifier != nkEmpty: + result = newNodeP(modifier, p) getTok(p) result.addSon(p.parseSymbol) else: diff --git a/compiler/semdata.nim b/compiler/semdata.nim index ef23e40f2..dcd1e04b4 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -230,6 +230,17 @@ proc makePtrType*(c: PContext, baseType: PType): PType = result = newTypeS(tyPtr, c) addSonSkipIntLit(result, baseType.assertNotNil) +proc makeTypeWithModifier*(c: PContext, + modifier: TTypeKind, + baseType: PType): PType = + assert modifier in {tyVar, tyPtr, tyRef, tyStatic, tyTypeDesc} + + if modifier in {tyVar, tyTypeDesc} and baseType.kind == modifier: + result = baseType + else: + result = newTypeS(modifier, c) + addSonSkipIntLit(result, baseType.assertNotNil) + proc makeVarType*(c: PContext, baseType: PType): PType = if baseType.kind == tyVar: result = baseType @@ -238,8 +249,11 @@ proc makeVarType*(c: PContext, baseType: PType): PType = addSonSkipIntLit(result, baseType.assertNotNil) proc makeTypeDesc*(c: PContext, typ: PType): PType = - result = newTypeS(tyTypeDesc, c) - result.addSonSkipIntLit(typ.assertNotNil) + if typ.kind == tyTypeDesc: + result = typ + else: + result = newTypeS(tyTypeDesc, c) + result.addSonSkipIntLit(typ.assertNotNil) proc makeTypeSymNode*(c: PContext, typ: PType, info: TLineInfo): PNode = let typedesc = makeTypeDesc(c, typ) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index e3c3123f9..bf6c24310 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -135,7 +135,7 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType = let isCall = ord(n.kind in nkCallKinds+{nkBracketExpr}) let n = if n[0].kind == nkBracket: n[0] else: n checkMinSonsLen(n, 1) - var base = semTypeNode(c, n.lastSon, nil) + var base = semTypeNode(c, n.lastSon, nil).skipTypes({tyTypeDesc}) result = newOrPrevType(kind, prev, c) var isNilable = false # check every except the last is an object: @@ -155,7 +155,7 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType = proc semVarType(c: PContext, n: PNode, prev: PType): PType = if sonsLen(n) == 1: result = newOrPrevType(tyVar, prev, c) - var base = semTypeNode(c, n.sons[0], nil) + var base = semTypeNode(c, n.sons[0], nil).skipTypes({tyTypeDesc}) if base.kind == tyVar: localError(n.info, errVarVarTypeNotAllowed) base = base.sons[0] @@ -1404,7 +1404,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of nkDistinctTy: result = semDistinct(c, n, prev) of nkStaticTy: result = newOrPrevType(tyStatic, prev, c) - var base = semTypeNode(c, n.sons[0], nil) + var base = semTypeNode(c, n.sons[0], nil).skipTypes({tyTypeDesc}) result.rawAddSon(base) result.flags.incl tfHasStatic of nkIteratorTy: diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 3dede8b6f..505e785d6 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -622,17 +622,27 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, dummyName: PNode dummyType: PType - if param.kind == nkVarTy: + let modifier = case param.kind + of nkVarTy: tyVar + of nkRefTy: tyRef + of nkPtrTy: tyPtr + of nkStaticTy: tyStatic + of nkTypeOfExpr: tyTypeDesc + else: tyNone + + if modifier != tyNone: dummyName = param[0] - dummyType = if a.kind != tyVar: makeVarType(c, a) else: a + dummyType = c.makeTypeWithModifier(modifier, a) else: dummyName = param dummyType = a internalAssert dummyName.kind == nkIdent - var dummyParam = newSym(skVar, dummyName.ident, body.sym, body.sym.info) + var dummyParam = newSym(if modifier == tyTypeDesc: skType else: skVar, + dummyName.ident, body.sym, body.sym.info) dummyParam.typ = dummyType addDecl(c, dummyParam) + #echo "B ", dummyName.ident.s, " ", typeToString(dummyType), " ", dummyparam.kind var checkedBody = c.semTryExpr(c, body.n[3].copyTree) @@ -1411,12 +1421,12 @@ proc inferTypeClassParam*(m: var TCandidate, f, a: PType): bool = if inferrableType == nil: return false var inferAs = f - + case f.kind of tyGenericParam: var prev = PType(idTableGet(m.bindings, f)) if prev != nil: inferAs = prev - + of tyFromExpr: let computedType = tryResolvingStaticExpr(m, f.n).typ case computedType.kind @@ -1426,10 +1436,10 @@ proc inferTypeClassParam*(m: var TCandidate, f, a: PType): bool = inferAs = computedType else: localError(f.n.info, errTypeExpected) - + else: discard - + inferrableType.assignType inferAs return true @@ -1470,7 +1480,8 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, argType = arg.typ var - useTypeLoweringRuleInTypeClass = c.inTypeClass > 0 and + useTypeLoweringRuleInTypeClass = argType != nil and + c.inTypeClass > 0 and not m.isNoCall and f.kind != tyTypeDesc @@ -1478,9 +1489,9 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, argType.skipTypes({tyTypeDesc, tyFieldAccessor}) else: argType - + r = typeRel(m, f, a) - + if r != isNone and m.calleeSym != nil and m.calleeSym.kind in {skMacro, skTemplate}: # XXX: duplicating this is ugly, but we cannot (!) move this diff --git a/tests/concepts/tusertypeclasses.nim b/tests/concepts/tusertypeclasses.nim index 612556949..9a20bfae5 100644 --- a/tests/concepts/tusertypeclasses.nim +++ b/tests/concepts/tusertypeclasses.nim @@ -2,16 +2,13 @@ discard """ output: '''Sortable Sortable Container -true -true -false -false -false ''' """ import typetraits +template reject(expr) = assert(not compiles(x)) + type TObj = object x: int @@ -36,33 +33,52 @@ foo(@[TObj(x: 10), TObj(x: 20)]) proc intval(x: int): int = 10 -# check real and virtual fields type - TFoo = concept T - T.x - y(T) + TFoo = concept o, type T, ref r, var v, ptr p, static s + o.x + y(o) is int + + var str: string + var intref: ref int + + refproc(ref T, ref int) + varproc(var T) + ptrproc(ptr T, str) + + staticproc(static[T]) + + typeproc o.type + o.type.typeproc + + refproc(r, intref) + varproc(v) + p.ptrproc(string) + staticproc s + typeproc(T) + + const TypeName = T.name + type MappedType = type(T.y) + intval T.y - let z = intval(T.y) + let z = intval(o.y) + + static: + assert T.name.len == 4 + reject o.name + reject o.typeproc + reject staticproc(o) + reject o.varproc + reject T.staticproc + reject p.staticproc proc y(x: TObj): int = 10 +proc varproc(x: var TObj) = discard +proc refproc(x: ref TObj, y: ref int) = discard +proc ptrproc(x: ptr TObj, y: string) = discard +proc staticproc(x: static[TObj]) = discard +proc typeproc(t: type TObj) = discard + proc testFoo(x: TFoo) = discard testFoo(TObj(x: 10)) -type - Matrix[Rows, Cols: static[int]; T] = concept M - M.M == Rows - M.N == Cols - M.T is T - - MyMatrix[M, N: static[int]; T] = object - data: array[M*N, T] - -var x: MyMatrix[3, 3, int] - -echo x is Matrix -echo x is Matrix[3, 3, int] -echo x is Matrix[3, 3, float] -echo x is Matrix[4, 3, int] -echo x is Matrix[3, 4, int] - From 8cd5f1f8f53e9b286c7ca5af5245f20c052d47eb Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Tue, 2 Aug 2016 01:15:46 +0300 Subject: [PATCH 06/37] introduce tyInferred for the unbound concept type params * Why is tyInferred needed? The bindings in TCandidate are capable of inferring types within a single call expression. In concepts, we need to infer types in the same way, but across the whole body of the concept. Previously, once a concept type param was inferred, it was destructively mutated using t.assignType, but this proved to be problematic in the presence of overloads, because the bindings established while a non-matching overload is tested must be reverted/forgotten. tyInferred offers a non-destructive way to keep track of the inference progress. While introducing new types usually requires a lot of code paths in the compiler to updated, currently tyInferred is only a short-lived type within the concept body pass and it's unlikely to introduce breakage elsewhere in the compiler. --- compiler/ast.nim | 34 +++++++--- compiler/ccgtypes.nim | 2 +- compiler/ccgutils.nim | 2 +- compiler/jsgen.nim | 2 + compiler/semasgn.nim | 2 +- compiler/semexprs.nim | 1 - compiler/sigmatch.nim | 151 ++++++++++++++++++++++-------------------- compiler/types.nim | 27 +++++--- compiler/vmdeps.nim | 1 + 9 files changed, 126 insertions(+), 96 deletions(-) diff --git a/compiler/ast.nim b/compiler/ast.nim index 4a25e0336..f7681222c 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -354,44 +354,52 @@ type tyUnused, tyProxy # used as errornous type (for idetools) - tyBuiltInTypeClass #\ + tyBuiltInTypeClass # Type such as the catch-all object, tuple, seq, etc - tyUserTypeClass #\ + tyUserTypeClass # the body of a user-defined type class - tyUserTypeClassInst #\ + tyUserTypeClassInst # Instance of a parametric user-defined type class. # Structured similarly to tyGenericInst. # tyGenericInst represents concrete types, while # this is still a "generic param" that will bind types # and resolves them during sigmatch and instantiation. - tyCompositeTypeClass #\ + tyCompositeTypeClass # Type such as seq[Number] # The notes for tyUserTypeClassInst apply here as well # sons[0]: the original expression used by the user. # sons[1]: fully expanded and instantiated meta type # (potentially following aliases) - tyAnd, tyOr, tyNot #\ + tyInferred + # In the initial state `base` stores a type class constraining + # the types that can be inferred. After a candidate type is + # selected, it's stored in `lastSon`. Between `base` and `lastSon` + # there may be 0, 2 or more types that were also considered as + # possible candidates in the inference process (i.e. lastSon will + # be updated to store a type best conforming to all candidates) + + tyAnd, tyOr, tyNot # boolean type classes such as `string|int`,`not seq`, # `Sortable and Enumable`, etc - tyAnything #\ + tyAnything # a type class matching any type - tyStatic #\ + tyStatic # a value known at compile type (the underlying type is .base) - tyFromExpr #\ + tyFromExpr # This is a type representing an expression that depends # on generic parameters (the expression is stored in t.n) # It will be converted to a real type only during generic # instantiation and prior to this it has the potential to # be any type. - tyFieldAccessor #\ + tyFieldAccessor # Expressions such as Type.field (valid in contexts such # as the `is` operator and magics like `high` and `low`). # Could be lifted to a single argument proc returning the @@ -400,7 +408,7 @@ type # sons[1]: field type # .n: nkDotExpr storing the field name - tyVoid #\ + tyVoid # now different from tyEmpty, hurray! static: @@ -482,7 +490,6 @@ type tfHasStatic tfGenericTypeParam tfImplicitTypeParam - tfInferrableTypeClassTypeParam tfWildcard # consider a proc like foo[T, I](x: Type[T, I]) # T and I here can bind to both typedesc and static types # before this is determined, we'll consider them to be a @@ -1037,6 +1044,9 @@ proc newStrNode*(kind: TNodeKind, strVal: string): PNode = result = newNode(kind) result.strVal = strVal +template previouslyInferred*(t: PType): PType = + if t.sons.len > 1: t.lastSon else: nil + proc newSym*(symKind: TSymKind, name: PIdent, owner: PSym, info: TLineInfo): PSym = # generates a symbol and initializes the hash field too @@ -1279,6 +1289,8 @@ proc copyType*(t: PType, owner: PSym, keepId: bool): PType = when debugIds: registerId(result) result.sym = t.sym # backend-info should not be copied +proc exactReplica*(t: PType): PType = copyType(t, t.owner, true) + proc copySym*(s: PSym, keepId: bool = false): PSym = result = newSym(s.kind, s.name, s.owner, s.info) #result.ast = nil # BUGFIX; was: s.ast which made problems diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 8fdd97428..0bbb6e414 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -165,7 +165,7 @@ proc mapType(typ: PType): TCTypeKind = of tyOpenArray, tyArray, tyVarargs: result = ctArray of tyObject, tyTuple: result = ctStruct of tyGenericBody, tyGenericInst, tyGenericParam, tyDistinct, tyOrdinal, - tyTypeDesc, tyAlias: + tyTypeDesc, tyAlias, tyInferred: result = mapType(lastSon(typ)) of tyEnum: if firstOrd(typ) < 0: diff --git a/compiler/ccgutils.nim b/compiler/ccgutils.nim index afcf24167..c37a8fcdb 100644 --- a/compiler/ccgutils.nim +++ b/compiler/ccgutils.nim @@ -110,7 +110,7 @@ proc getUniqueType*(key: PType): PType = of tyDistinct: if key.deepCopy != nil: result = key else: result = getUniqueType(lastSon(key)) - of tyGenericInst, tyOrdinal, tyStatic, tyAlias: + of tyGenericInst, tyOrdinal, tyStatic, tyAlias, tyInferred: result = getUniqueType(lastSon(key)) #let obj = lastSon(key) #if obj.sym != nil and obj.sym.name.s == "TOption": diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index f5e8027b3..ff467c986 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -167,6 +167,8 @@ proc mapType(typ: PType): TJSTypeKind = tyNone, tyFromExpr, tyForward, tyEmpty, tyFieldAccessor, tyExpr, tyStmt, tyTypeDesc, tyTypeClasses, tyVoid, tyAlias: result = etyNone + of tyInferred: + result = mapType(typ.lastSon) of tyStatic: if t.n != nil: result = mapType(lastSon t) else: result = etyNone diff --git a/compiler/semasgn.nim b/compiler/semasgn.nim index 70765d087..f2144037c 100644 --- a/compiler/semasgn.nim +++ b/compiler/semasgn.nim @@ -226,7 +226,7 @@ proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) = tyGenericParam, tyGenericBody, tyNil, tyExpr, tyStmt, tyTypeDesc, tyGenericInvocation, tyForward: internalError(c.info, "assignment requested for type: " & typeToString(t)) - of tyOrdinal, tyRange, + of tyOrdinal, tyRange, tyInferred, tyGenericInst, tyFieldAccessor, tyStatic, tyVar, tyAlias: liftBodyAux(c, lastSon(t), body, x, y) of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("liftBodyAux") diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index d81abe20e..74c4ae5d5 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -323,7 +323,6 @@ proc isOpImpl(c: PContext, n: PNode): PNode = maybeLiftType(t2, c, n.info) var m: TCandidate initCandidate(c, m, t2) - discard inferTypeClassParam(m, t1, rhsOrigType) let match = typeRel(m, t2, t1) >= isSubtype # isNone result = newIntNode(nkIntLit, ord(match)) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 505e785d6..0dee70139 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -49,6 +49,10 @@ type # a distrinct type typedescMatched*: bool isNoCall*: bool # misused for generic type instantiations C[T] + inferredTypes: seq[PType] # inferred types during the current signature + # matching. they will be reset if the matching + # is not successful. may replace the bindings + # table in the future. mutabilityProblem*: uint8 # tyVar mismatch inheritancePenalty: int # to prefer closest father object type errors*: CandidateErrors # additional clarifications to be displayed to the @@ -600,17 +604,17 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, case typ.kind of tyStatic: param = paramSym skConst - param.typ = typ.base + param.typ = typ.exactReplica param.ast = typ.n of tyUnknown: param = paramSym skVar - param.typ = typ + param.typ = typ.exactReplica else: param = paramSym skType - param.typ = makeTypeDesc(c, typ) - - if typ.isMetaType: - param.typ.flags.incl tfInferrableTypeClassTypeParam + param.typ = if typ.isMetaType: + c.newTypeWithSons(tyInferred, @[typ]) + else: + makeTypeDesc(c, typ) addDecl(c, param) typeParams.safeAdd((param, typ)) @@ -718,8 +722,51 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = assert(aOrig != nil) + var + useTypeLoweringRuleInTypeClass = c.c.inTypeClass > 0 and + not c.isNoCall and + f.kind != tyTypeDesc + + aOrig = if useTypeLoweringRuleInTypeClass: + aOrig.skipTypes({tyTypeDesc, tyFieldAccessor}) + else: + aOrig + + if aOrig.kind == tyInferred: + # echo "INFER A" + # debug f + # debug aOrig + let prev = aOrig.previouslyInferred + if prev != nil: + return typeRel(c, f, prev) + else: + var candidate = f + + case f.kind + of tyGenericParam: + var prev = PType(idTableGet(c.bindings, f)) + if prev != nil: candidate = prev + of tyFromExpr: + let computedType = tryResolvingStaticExpr(c, f.n).typ + case computedType.kind + of tyTypeDesc: + candidate = computedType.base + of tyStatic: + candidate = computedType + else: + localError(f.n.info, errTypeExpected) + else: + discard + + result = typeRel(c, aOrig.base, candidate) + if result != isNone: + c.inferredTypes.safeAdd aOrig + aOrig.sons.add candidate + result = isEqual + return + # var and static arguments match regular modifier-free types - let a = aOrig.skipTypes({tyStatic, tyVar}).maybeSkipDistinct(c.calleeSym) + var a = aOrig.skipTypes({tyStatic, tyVar}).maybeSkipDistinct(c.calleeSym) # XXX: Theoretically, maybeSkipDistinct could be called before we even # start the param matching process. This could be done in `prepareOperand` # for example, but unfortunately `prepareOperand` is not called in certain @@ -1258,6 +1305,20 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = # XXX endless recursion? #result = typeRel(c, prev, aOrig) result = isNone + + of tyInferred: + # echo "INFER F" + # debug f + # debug a + let prev = f.previouslyInferred + if prev != nil: + result = typeRel(c, prev, a) + else: + result = typeRel(c, f.base, a) + if result != isNone: + c.inferredTypes.safeAdd f + f.sons.add a + of tyTypeDesc: var prev = PType(idTableGet(c.bindings, f)) if prev == nil: @@ -1401,59 +1462,14 @@ proc incMatches(m: var TCandidate; r: TTypeRelation; convMatch = 1) = of isEqual: inc(m.exactMatches) of isNone: discard -proc skipToInferrableParam(tt: PType): PType = - var t = tt - while t != nil: - if tfInferrableTypeClassTypeParam in t.flags: - return t - if t.sonsLen > 0 and t.kind == tyTypeDesc: - t = t.base - else: - return nil - - return nil - -proc inferTypeClassParam*(m: var TCandidate, f, a: PType): bool = - var c = m.c - if c.inTypeClass == 0: return false - - var inferrableType = a.skipToInferrableParam - if inferrableType == nil: return false - - var inferAs = f - - case f.kind - of tyGenericParam: - var prev = PType(idTableGet(m.bindings, f)) - if prev != nil: inferAs = prev - - of tyFromExpr: - let computedType = tryResolvingStaticExpr(m, f.n).typ - case computedType.kind - of tyTypeDesc: - inferAs = computedType.base - of tyStatic: - inferAs = computedType - else: - localError(f.n.info, errTypeExpected) - - else: - discard - - inferrableType.assignType inferAs - return true - -proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, +proc paramTypesMatchAux(m: var TCandidate, f, a: PType, argSemantized, argOrig: PNode): PNode = var fMaybeStatic = f.skipTypes({tyDistinct}) arg = argSemantized - argType = argType + a = a c = m.c - if inferTypeClassParam(m, f, argType): - return argSemantized - if tfHasStatic in fMaybeStatic.flags: # XXX: When implicit statics are the default # this will be done earlier - we just have to @@ -1461,12 +1477,12 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, # XXX: weaken tyGenericParam and call it tyGenericPlaceholder # and finally start using tyTypedesc for generic types properly. - if argType.kind == tyGenericParam and tfWildcard in argType.flags: - argType.assignType(f) - # put(m.bindings, f, argType) + if a.kind == tyGenericParam and tfWildcard in a.flags: + a.assignType(f) + # put(m.bindings, f, a) return argSemantized - if argType.kind == tyStatic: + if a.kind == tyStatic: if m.callee.kind == tyGenericBody and argType.n == nil and tfGenericTypeParam notin argType.flags: @@ -1477,20 +1493,9 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, arg.typ = newTypeS(tyStatic, c) arg.typ.sons = @[evaluated.typ] arg.typ.n = evaluated - argType = arg.typ + a = arg.typ - var - useTypeLoweringRuleInTypeClass = argType != nil and - c.inTypeClass > 0 and - not m.isNoCall and - f.kind != tyTypeDesc - - a = if useTypeLoweringRuleInTypeClass: - argType.skipTypes({tyTypeDesc, tyFieldAccessor}) - else: - argType - - r = typeRel(m, f, a) + var r = typeRel(m, f, a) if r != isNone and m.calleeSym != nil and m.calleeSym.kind in {skMacro, skTemplate}: @@ -1931,6 +1936,10 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) = def = implicitConv(nkHiddenStdConv, formal.typ, def, m, c) setSon(m.call, formal.position + 1, def) inc(f) + # forget all inferred types if the overload matching failed + if m.state == csNoMatch: + for t in m.inferredTypes: + if t.sonsLen > 1: t.sons.setLen 1 proc argtypeMatches*(c: PContext, f, a: PType): bool = var m: TCandidate diff --git a/compiler/types.nim b/compiler/types.nim index 285854aa0..3e124412b 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -55,16 +55,17 @@ const # TODO: Remove tyTypeDesc from each abstractX and (where necessary) # replace with typedescX abstractPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyDistinct, tyOrdinal, - tyTypeDesc, tyAlias} + tyTypeDesc, tyAlias, tyInferred} abstractVar* = {tyVar, tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, - tyAlias} + tyAlias, tyInferred} abstractRange* = {tyGenericInst, tyRange, tyDistinct, tyOrdinal, tyTypeDesc, - tyAlias} + tyAlias, tyInferred} abstractVarRange* = {tyGenericInst, tyRange, tyVar, tyDistinct, tyOrdinal, - tyTypeDesc, tyAlias} - abstractInst* = {tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias} - - skipPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyTypeDesc, tyAlias} + tyTypeDesc, tyAlias, tyInferred} + abstractInst* = {tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, + tyInferred} + skipPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyTypeDesc, tyAlias, + tyInferred} # typedescX is used if we're sure tyTypeDesc should be included (or skipped) typedescPtrs* = abstractPtrs + {tyTypeDesc} typedescInst* = abstractInst + {tyTypeDesc} @@ -410,7 +411,7 @@ const "unused0", "unused1", "unused2", "varargs[$1]", "unused", "Error Type", "BuiltInTypeClass", "UserTypeClass", - "UserTypeClassInst", "CompositeTypeClass", + "UserTypeClassInst", "CompositeTypeClass", "inferred", "and", "or", "not", "any", "static", "TypeFromExpr", "FieldAccessor", "void"] @@ -476,6 +477,10 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = of tyTuple: "tuple" of tyOpenArray: "openarray" else: typeToStr[t.base.kind] + of tyInferred: + let concrete = t.previouslyInferred + if concrete != nil: result = typeToString(concrete) + else: result = "inferred[" & typeToString(t.base) & "]" of tyUserTypeClassInst: let body = t.base result = body.sym.name.s & "[" @@ -971,7 +976,9 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool = result = sameTypeOrNilAux(a.sons[0], b.sons[0], c) and sameValue(a.n.sons[0], b.n.sons[0]) and sameValue(a.n.sons[1], b.n.sons[1]) - of tyGenericInst, tyAlias: discard + of tyGenericInst, tyAlias, tyInferred: + cycleCheck() + result = sameTypeAux(a.lastSon, b.lastSon, c) of tyNone: result = false of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("sameFlags") @@ -1118,7 +1125,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, result = nil of tyOrdinal: if kind != skParam: result = t - of tyGenericInst, tyDistinct, tyAlias: + of tyGenericInst, tyDistinct, tyAlias, tyInferred: result = typeAllowedAux(marker, lastSon(t), kind, flags) of tyRange: if skipTypes(t.sons[0], abstractInst-{tyTypeDesc}).kind notin diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim index d684c4c32..7094d174b 100644 --- a/compiler/vmdeps.nim +++ b/compiler/vmdeps.nim @@ -294,6 +294,7 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; of tyOr: result = mapTypeToBracket("or", mOr, t, info) of tyNot: result = mapTypeToBracket("not", mNot, t, info) of tyAnything: result = atomicType("anything", mNone) + of tyInferred: internalAssert false of tyStatic, tyFromExpr, tyFieldAccessor: if inst: if t.n != nil: result = t.n.copyTree From 76c663f692999b8d9099ee89ee9e4d1111707d74 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Tue, 2 Aug 2016 05:15:19 +0300 Subject: [PATCH 07/37] support for inferring static[T] concept params --- compiler/ast.nim | 4 ++ compiler/ccgtypes.nim | 3 ++ compiler/semdata.nim | 16 ++++++-- compiler/semstmts.nim | 20 +++++++-- compiler/semtypinst.nim | 1 + compiler/sigmatch.nim | 46 +++++++++++---------- tests/concepts/tmatrixconcept.nim | 67 +++++++++++++++++++++++++++++++ tests/concepts/tvectorspace.nim | 15 +++++++ 8 files changed, 144 insertions(+), 28 deletions(-) create mode 100644 tests/concepts/tmatrixconcept.nim create mode 100644 tests/concepts/tvectorspace.nim diff --git a/compiler/ast.nim b/compiler/ast.nim index f7681222c..bd244fb97 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -490,6 +490,7 @@ type tfHasStatic tfGenericTypeParam tfImplicitTypeParam + tfInferrableStatic tfWildcard # consider a proc like foo[T, I](x: Type[T, I]) # T and I here can bind to both typedesc and static types # before this is determined, we'll consider them to be a @@ -1073,6 +1074,9 @@ proc isMetaType*(t: PType): bool = (t.kind == tyStatic and t.n == nil) or tfHasMeta in t.flags +proc isUnresolvedStatic*(t: PType): bool = + return t.kind == tyStatic and t.n == nil + proc linkTo*(t: PType, s: PSym): PType {.discardable.} = t.sym = s s.typ = t diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 0bbb6e414..d62eab8ac 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -164,6 +164,9 @@ proc mapType(typ: PType): TCTypeKind = of tySet: result = mapSetType(typ) of tyOpenArray, tyArray, tyVarargs: result = ctArray of tyObject, tyTuple: result = ctStruct + of tyUserTypeClass, tyUserTypeClassInst: + internalAssert typ.isResolvedUserTypeClass + return mapType(typ.lastSon) of tyGenericBody, tyGenericInst, tyGenericParam, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, tyInferred: result = mapType(lastSon(typ)) diff --git a/compiler/semdata.nim b/compiler/semdata.nim index dcd1e04b4..5cd755607 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -273,7 +273,8 @@ proc newTypeWithSons*(c: PContext, kind: TTypeKind, proc makeStaticExpr*(c: PContext, n: PNode): PNode = result = newNodeI(nkStaticExpr, n.info) result.sons = @[n] - result.typ = newTypeWithSons(c, tyStatic, @[n.typ]) + result.typ = if n.typ != nil and n.typ.kind == tyStatic: n.typ + else: newTypeWithSons(c, tyStatic, @[n.typ]) proc makeAndType*(c: PContext, t1, t2: PType): PType = result = newTypeS(tyAnd, c) @@ -317,16 +318,23 @@ proc makeRangeWithStaticExpr*(c: PContext, n: PNode): PType = let intType = getSysType(tyInt) result = newTypeS(tyRange, c) result.sons = @[intType] + if n.typ.n == nil: result.flags.incl tfUnresolved result.n = newNode(nkRange, n.info, @[ newIntTypeNode(nkIntLit, 0, intType), makeStaticExpr(c, n.nMinusOne)]) -template rangeHasStaticIf*(t: PType): bool = +template rangeHasUnresolvedStatic*(t: PType): bool = # this accepts the ranges's node t.n != nil and t.n.len > 1 and t.n[1].kind == nkStaticExpr -template getStaticTypeFromRange*(t: PType): PType = - t.n[1][0][1].typ +proc findUnresolvedStaticInRange*(t: PType): (PType, int) = + assert t.kind == tyRange + # XXX: This really needs to become more sophisticated + let upperBound = t.n[1] + if upperBound[0].kind == nkCall: + return (upperBound[0][1].typ, 1) + else: + return (upperBound.typ, 0) proc errorType*(c: PContext): PType = ## creates a type representing an error state diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 33b684e91..64449dda4 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1553,6 +1553,11 @@ proc usesResult(n: PNode): bool = for c in n: if usesResult(c): return true +proc inferConceptStaticParam(c: PContext, typ: PType, n: PNode) = + let res = semConstExpr(c, n) + if not sameType(res.typ, typ.base): localError(n.info, "") + typ.n = res + proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = # these must be last statements in a block: const @@ -1604,10 +1609,19 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = n.typ = n.sons[i].typ return else: - n.sons[i] = semExpr(c, n.sons[i]) - if c.inTypeClass > 0 and n[i].typ != nil: - case n[i].typ.kind + var expr = semExpr(c, n.sons[i]) + n.sons[i] = expr + if c.inTypeClass > 0 and expr.typ != nil: + case expr.typ.kind of tyBool: + if expr.kind == nkInfix and expr[0].sym.name.s == "==": + if expr[1].typ.isUnresolvedStatic: + inferConceptStaticParam(c, expr[1].typ, expr[2]) + continue + elif expr[2].typ.isUnresolvedStatic: + inferConceptStaticParam(c, expr[2].typ, expr[1]) + continue + let verdict = semConstExpr(c, n[i]) if verdict.intVal == 0: localError(result.info, "type class predicate failed") diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 9a1ace42e..7e114afb8 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -122,6 +122,7 @@ proc isTypeParam(n: PNode): bool = proc hasGenericArguments*(n: PNode): bool = if n.kind == nkSym: return n.sym.kind == skGenericParam or + tfInferrableStatic in n.sym.typ.flags or (n.sym.kind == skType and n.sym.typ.flags * {tfGenericTypeParam, tfImplicitTypeParam} != {}) else: diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 0dee70139..f33ac76e7 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -605,7 +605,10 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, of tyStatic: param = paramSym skConst param.typ = typ.exactReplica - param.ast = typ.n + if typ.n == nil: + param.typ.flags.incl tfInferrableStatic + else: + param.ast = typ.n of tyUnknown: param = paramSym skVar param.typ = typ.exactReplica @@ -619,8 +622,6 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, addDecl(c, param) typeParams.safeAdd((param, typ)) - #echo "A ", param.name.s, " ", typeToString(param.typ), " ", param.kind - for param in body.n[0]: var dummyName: PNode @@ -647,8 +648,6 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, dummyParam.typ = dummyType addDecl(c, dummyParam) - #echo "B ", dummyName.ident.s, " ", typeToString(dummyType), " ", dummyparam.kind - var checkedBody = c.semTryExpr(c, body.n[3].copyTree) if checkedBody == nil: return nil @@ -733,9 +732,6 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = aOrig if aOrig.kind == tyInferred: - # echo "INFER A" - # debug f - # debug aOrig let prev = aOrig.previouslyInferred if prev != nil: return typeRel(c, f, prev) @@ -886,6 +882,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = case a.kind of tyArray: var fRange = f.sons[0] + var aRange = a.sons[0] if fRange.kind == tyGenericParam: var prev = PType(idTableGet(c.bindings, fRange)) if prev == nil: @@ -893,21 +890,28 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = fRange = a else: fRange = prev - result = typeRel(c, f.sons[1], a.sons[1]) + result = typeRel(c, f.sons[1].skipTypes({tyTypeDesc}), + a.sons[1].skipTypes({tyTypeDesc})) if result < isGeneric: return isNone - if rangeHasStaticIf(fRange): + + proc inferStaticRange(c: var TCandidate, inferred, concrete: PType) = + var (staticT, offset) = inferred.findUnresolvedStaticInRange + var + replacementT = newTypeWithSons(c.c, tyStatic, @[tyInt.getSysType]) + concreteUpperBound = concrete.n[1].intVal + # we must correct for the off-by-one discrepancy between + # ranges and static params: + replacementT.n = newIntNode(nkIntLit, concreteUpperBound + offset) + if tfInferrableStatic in staticT.flags: + staticT.n = replacementT.n + put(c.bindings, staticT, replacementT) + + if rangeHasUnresolvedStatic(fRange): if tfUnresolved in fRange.flags: # This is a range from an array instantiated with a generic # static param. We must extract the static param here and bind # it to the size of the currently supplied array. - var - rangeStaticT = fRange.getStaticTypeFromRange - replacementT = newTypeWithSons(c.c, tyStatic, @[tyInt.getSysType]) - inputUpperBound = a.sons[0].n[1].intVal - # we must correct for the off-by-one discrepancy between - # ranges and static params: - replacementT.n = newIntNode(nkIntLit, inputUpperBound + 1) - put(c, rangeStaticT, replacementT) + inferStaticRange(c, fRange, aRange) return isGeneric let len = tryResolvingStaticExpr(c, fRange.n[1]) @@ -915,6 +919,9 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = return # if we get this far, the result is already good else: return isNone + elif c.c.inTypeClass > 0 and aRange.rangeHasUnresolvedStatic: + inferStaticRange(c, aRange, fRange) + return isGeneric elif lengthOrd(fRange) != lengthOrd(a): result = isNone else: discard @@ -1307,9 +1314,6 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = result = isNone of tyInferred: - # echo "INFER F" - # debug f - # debug a let prev = f.previouslyInferred if prev != nil: result = typeRel(c, prev, a) diff --git a/tests/concepts/tmatrixconcept.nim b/tests/concepts/tmatrixconcept.nim new file mode 100644 index 000000000..4bc002dd4 --- /dev/null +++ b/tests/concepts/tmatrixconcept.nim @@ -0,0 +1,67 @@ +discard """ +output: "0\n0" +msg: ''' +R=3 C=3 TE=9 FF=14 FC=20 T=int +''' +""" + +import typetraits + +template ok(x) = assert x +template no(x) = assert(not x) + +const C = 10 + +type + Matrix[Rows, Cols, TotalElements, FromFoo, FromConst: static[int]; T] = concept m, var mvar, type M + M.M == Rows + Cols == M.N + M.T is T + + m[int, int] is T + mvar[int, int] = T + + FromConst == C * 2 + + # more complicated static param inference cases + m.data is array[TotalElements, T] + M.foo(array[0..FromFoo, type m[int, 10]]) + + MyMatrix[M, K: static[int]; T] = object + data: array[M*K, T] + +# adaptor for the concept's non-matching expectations +template N(M: type MyMatrix): expr = M.K + +proc `[]`(m: MyMatrix; r, c: int): m.T = + m.data[r * m.K + c] + +proc `[]=`(m: var MyMatrix; r, c: int, v: m.T) = + m.data[r * m.K + c] = v + +proc foo(x: MyMatrix, arr: array[15, x.T]) = discard + +proc matrixProc[R, C, TE, FF, FC, T](m: Matrix[R, C, TE, FF, FC, T]): T = + static: + echo "R=", R, " C=", C, " TE=", TE, " FF=", FF, " FC=", FC, " T=", T.name + + m[0, 0] + +proc myMatrixProc(x: MyMatrix): MyMatrix.T = matrixProc(x) + +var x: MyMatrix[3, 3, int] + +static: + # ok x is Matrix + ok x is Matrix[3, 3, 9, 14, 20, int] + + no x is Matrix[3, 3, 8, 15, 20, int] + no x is Matrix[3, 3, 9, 10, 20, int] + no x is Matrix[3, 3, 9, 15, 21, int] + no x is Matrix[3, 3, 9, 15, 20, float] + no x is Matrix[4, 3, 9, 15, 20, int] + no x is Matrix[3, 4, 9, 15, 20, int] + +echo x.myMatrixProc +echo x.matrixProc + diff --git a/tests/concepts/tvectorspace.nim b/tests/concepts/tvectorspace.nim new file mode 100644 index 000000000..74423e0d2 --- /dev/null +++ b/tests/concepts/tvectorspace.nim @@ -0,0 +1,15 @@ +type VectorSpace[K] = concept x, y + x + y is type(x) + zero(type(x)) is type(x) + -x is type(x) + x - y is type(x) + var k: K + k * x is type(x) + +proc zero(T: typedesc): T = 0 + +static: + assert float is VectorSpace[float] + # assert float is VectorSpace[int] + # assert int is VectorSpace + From 19918ad96f7ba40e4656e5881290cfc4cfd20424 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Thu, 4 Aug 2016 04:35:03 +0300 Subject: [PATCH 08/37] future manual additions for the full concept spec I'm aiming to implement --- doc/manual/generics.txt | 380 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 361 insertions(+), 19 deletions(-) diff --git a/doc/manual/generics.txt b/doc/manual/generics.txt index c1c6467e7..1fca531aa 100644 --- a/doc/manual/generics.txt +++ b/doc/manual/generics.txt @@ -148,8 +148,8 @@ as `type constraints`:idx: of the generic type parameter: onlyIntOrString("xy", 50) # invalid as 'T' cannot be both at the same time By default, during overload resolution each named type class will bind to -exactly one concrete type. Here is an example taken directly from the system -module to illustrate this: +exactly one concrete type. We call such type classes `bind once`:idx: types. +Here is an example taken directly from the system module to illustrate this: .. code-block:: nim proc `==`*(x, y: tuple): bool = @@ -161,7 +161,8 @@ module to illustrate this: if a != b: result = false Alternatively, the ``distinct`` type modifier can be applied to the type class -to allow each param matching the type class to bind to a different type. +to allow each param matching the type class to bind to a different type. Such +type classes are called `bind many`:idx: types. Procs written with the implicitly generic style will often need to refer to the type parameters of the matched generic type. They can be easily accessed using @@ -211,34 +212,375 @@ Concepts are written in the following form: Comparable = concept x, y (x < y) is bool - Container[T] = concept c - c.len is Ordinal - items(c) is T - for value in c: - type(value) is T + Stack[T] = concept s, var v + s.pop() is T + v.push(T) + + s.len is Ordinal + + for value in s: + value is T The concept is a match if: a) all of the expressions within the body can be compiled for the tested type -b) all statically evaluatable boolean expressions in the body must be true +b) all statically evaluable boolean expressions in the body must be true The identifiers following the ``concept`` keyword represent instances of the -currently matched type. These instances can act both as variables of the type, -when used in contexts where a value is expected, and as the type itself when -used in contexts where a type is expected. +currently matched type. You can apply any of the standard type modifiers such +as ``var``, ``ref``, ``ptr`` and ``static`` to denote a more specific type of +instance. You can also apply the `type` modifier to create a named instance of +the type itself: + +.. code-block:: nim + type + MyConcept = concept x, var v, ref r, ptr p, static s, type T + ... + +Within the concept body, types can appear in positions where ordinary values +and parameters are expected. This provides a more convenient way to check for +the presence of callable symbols with specific signatures: + +.. code-block:: nim + type + OutputStream = concept var s + s.write(string) + +In order to check for symbols accepting ``typedesc`` params, you must prefix +the type with an explicit ``type`` modifier. The named instance of the type, +following the ``concept`` keyword is also considered an explicit ``typedesc`` +value that will be matched only as a type. + +.. code-block:: nim + type + # Let's imagine a user-defined casting framework with operators + # such as `val.to(string)` and `val.to(JSonValue)`. We can test + # for these with the following concept: + MyCastables = concept x + x.to(type string) + x.to(type JSonValue) + + # Let's define a couple of concepts, known from Algebra: + AdditiveMonoid* = concept x, y, type T + x + y is T + T.zero is T # require a proc such as `int.zero` or 'Position.zero' + + AdditiveGroup* = concept x, y, type T + x is AdditiveMonoid + -x is T + x - y is T Please note that the ``is`` operator allows one to easily verify the precise type signatures of the required operations, but since type inference and -default parameters are still applied in the provided block, it's also possible +default parameters are still applied in the concept body, it's also possible to encode usage protocols that do not reveal implementation details. -Much like generics, concepts are instantiated exactly -once for each tested type and any static code included within them is also -executed once. +Much like generics, concepts are instantiated exactly once for each tested type +and any static code included within the body is executed only once. -**Hint**: Since concepts are still very rough at the edges there is a -command line switch ``--reportConceptFailures:on`` to make debugging -concept related type failures more easy. + +Concept diagnostics +------------------- + +By default, the compiler will report the matching errors in concepts only when +no other overload can be selected and a normal compilation error is produced. +When you need to understand why the compiler is not matching a particular +concept and, as a result, a wrong overload is selected, you can apply the +``explain`` pragma to either the concept body or a particular call-site. + +.. code-block:: nim + type + MyConcept {.explain.} = concept ... + + overloadedProc(x, y, z) {.explain.} + +This will provide Hints in the compiler output either every time the concept is +not matched or only on the particular call-site. + + +Generic concepts and type binding rules +--------------------------------------- + +The concept types can be parametric just like the regular generic types: + +.. code-block:: nim + ### matrixalgo.nim + type + AnyMatrix*[R, C: static[int]; T] = concept m, var mvar, type M + M.ValueType is T + M.Rows == R + M.Cols == C + + m[int, int] is T + mvar[int, int] = T + + AnySquareMatrix*[N: static[int], T] = AnyMatrix[N, N, T] + + proc transpose*[R, C, T](m: AnyMatrix[R, C, T]): m.type.basis[C, R, T] = + for r in 0 .. Date: Thu, 4 Aug 2016 04:36:04 +0300 Subject: [PATCH 09/37] kill the reportConceptFailures options as it will be replaced by the .explain. pragma --- compiler/commands.nim | 2 -- compiler/options.nim | 1 - compiler/semcall.nim | 7 ++----- compiler/semexprs.nim | 5 +---- doc/advopt.txt | 3 --- 5 files changed, 3 insertions(+), 15 deletions(-) diff --git a/compiler/commands.nim b/compiler/commands.nim index 1b42a191e..22e4b5a2c 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -478,8 +478,6 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; of "linedir": processOnOffSwitch({optLineDir}, arg, pass, info) of "assertions", "a": processOnOffSwitch({optAssert}, arg, pass, info) of "deadcodeelim": processOnOffSwitchG({optDeadCodeElim}, arg, pass, info) - of "reportconceptfailures": - processOnOffSwitchG({optReportConceptFailures}, arg, pass, info) of "threads": processOnOffSwitchG({optThreads}, arg, pass, info) #if optThreads in gGlobalOptions: incl(gNotes, warnGcUnsafe) diff --git a/compiler/options.nim b/compiler/options.nim index 6372cddac..063dff826 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -41,7 +41,6 @@ type # please make sure we have under 32 options TGlobalOption* = enum # **keep binary compatible** gloptNone, optForceFullMake, optDeadCodeElim, optListCmd, optCompileOnly, optNoLinking, - optReportConceptFailures, # report 'compiles' or 'concept' matching failures optCDebug, # turn on debugging information optGenDynLib, # generate a dynamic library optGenStaticLib, # generate a static library diff --git a/compiler/semcall.nim b/compiler/semcall.nim index ffa940291..b440d1e75 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -118,7 +118,7 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) = # Gives a detailed error message; this is separated from semOverloadedCall, # as semOverlodedCall is already pretty slow (and we need this information # only in case of an error). - if c.compilesContextId > 0 and optReportConceptFailures notin gGlobalOptions: + if c.compilesContextId > 0: # fail fast: globalError(n.info, errTypeMismatch, "") if errors.isNil or errors.len == 0: @@ -163,10 +163,7 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) = add(candidates, "for a 'var' type a variable needs to be passed, but '" & renderTree(n[mut]) & "' is immutable\n") if candidates != "": add(result, "\n" & msgKindToString(errButExpected) & "\n" & candidates) - if c.compilesContextId > 0 and optReportConceptFailures in gGlobalOptions: - globalError(n.info, errGenerated, result) - else: - localError(n.info, errGenerated, result) + localError(n.info, errGenerated, result) proc bracketNotFoundError(c: PContext; n: PNode) = var errors: CandidateErrors = @[] diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 74c4ae5d5..475f496f1 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -1755,8 +1755,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = result = semExpr(c, n, flags) if msgs.gErrorCounter != oldErrorCount: result = nil except ERecoverableError: - if optReportConceptFailures in gGlobalOptions: - err = getCurrentExceptionMsg() + discard # undo symbol table changes (as far as it's possible): c.compilesContextId = oldCompilesId c.generics = oldGenerics @@ -1770,8 +1769,6 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = errorOutputs = oldErrorOutputs msgs.gErrorCounter = oldErrorCount msgs.gErrorMax = oldErrorMax - if optReportConceptFailures in gGlobalOptions and not err.isNil: - localError(n.info, err) proc semCompiles(c: PContext, n: PNode, flags: TExprFlags): PNode = # we replace this node by a 'true' or 'false' node: diff --git a/doc/advopt.txt b/doc/advopt.txt index 991f06397..9a9dab7e0 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -54,9 +54,6 @@ Advanced options: --embedsrc embeds the original source code as comments in the generated output --threadanalysis:on|off turn thread analysis on|off - --reportConceptFailures:on|off - show errors for 'system.compiles' and concept - evaluation --tlsEmulation:on|off turn thread local storage emulation on|off --taintMode:on|off turn taint mode on|off --implicitStatic:on|off turn implicit compile time evaluation on|off From 77a4512ae8e6e81173a0f3a862c51dc2308dc76c Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Sat, 6 Aug 2016 12:04:12 +0300 Subject: [PATCH 10/37] some revisions to the Concepts and VTable types specs --- doc/manual/generics.txt | 49 ++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/doc/manual/generics.txt b/doc/manual/generics.txt index 1fca531aa..962daa9c4 100644 --- a/doc/manual/generics.txt +++ b/doc/manual/generics.txt @@ -365,7 +365,7 @@ operator and also when types dependent on them are being matched: .. code-block:: nim type MyConcept[M, N: static[int]; T] = concept x - x.foo(SquareMatrix[N, T]) is array[M, int]` + x.foo(SquareMatrix[N, T]) is array[M, int] Nim may include a simple linear equation solver in the future to help us infer static params when arithmetic is involved. @@ -496,7 +496,7 @@ object inheritance syntax involving the ``of`` keyword: Converter type classes ---------------------- -Concepts can also be used to reduce a whole range of types to a single type or +Concepts can also be used to convert a whole range of types to a single type or a small set of simpler types. This is achieved with a `return` statement within the concept body: @@ -516,13 +516,13 @@ the concept body: # StringRefValue[wchar] return makeStringRefValue(x) - # this proc will have only two instantiations for the two character types - # the varargs param will be converted to an array of StringRefValues + # the varargs param will here be converted to an array of StringRefValues + # the proc will have only two instantiations for the two character types proc log(format: static[string], varargs[StringRef]) - # this proc will allow char and wchar values to be mixed in the same call - # at the cost of additional instantiations. the varargs param will be - # converted to a tuple + # this proc will allow char and wchar values to be mixed in + # the same call at the cost of additional instantiations + # the varargs param will be converted to a tuple proc log(format: static[string], varargs[distinct StringRef]) @@ -540,38 +540,37 @@ object together with a reference to a table of procs implementing a set of required operations (the so called vtable). In contrast to other programming languages, the vtable in Nim is stored -externally to the object, allowing you to create multiple vtable views for -the same object. Thus, the polymorphism in Nim is unbounded - any type can -implement an unlimited number of protocols or interfaces not originally -envisioned by the type's author. +externally to the object, allowing you to create multiple different vtable +views for the same object. Thus, the polymorphism in Nim is unbounded - +any type can implement an unlimited number of protocols or interfaces not +originally envisioned by the type's author. -Any concept type can be turned into a VTable type by using the ``vtable`` -or the ``ptrvtable`` compiler magics. Under the hood, these magics generate +Any concept type can be turned into a VTable type by using the ``vtref`` +or the ``vtptr`` compiler magics. Under the hood, these magics generate a converter type class, which converts the regular instances of the matching types to the corresponding VTable type. .. code-block:: nim type - IntEnumerable = vtable Enumerable[int] + IntEnumerable = vtref Enumerable[int] MyObject = object enumerables: seq[IntEnumerable] - additives: seq[AdditiveGroup.vtable] + streams: seq[OutputStream.vtref] proc addEnumerable(o: var MyObject, e: IntEnumerable) = o.enumerables.add e - proc addAdditive(o: var MyObject, e: AdditiveGroup.vtable) = - o.additives.add e + proc addStream(o: var MyObject, e: OutputStream.vtref) = + o.streams.add e The procs that will be included in the vtable are derived from the concept -body and include all proc calls for which all param types were inferred -successfully to concrete types. All such calls should include at least one -param of the type matched against the concept (not necessarily in the first -position). If there is more than one such param, the one appearing closest -to the ``concept`` keyword is considered the value bound to the vtable. +body and include all proc calls for which all param types were specified as +concrete types. All such calls should include exactly one param of the type +matched against the concept (not necessarily in the first position), which +will be considered the value bound to the vtable. -Overloads will be created for all captured procs, accepting the vtable value +Overloads will be created for all captured procs, accepting the vtable type in the position of the captured underlying object. Under these rules, it's possible to obtain a vtable type for a concept with @@ -579,8 +578,8 @@ unbound type parameters or one instantiated with metatypes (type classes), but it will include a smaller number of captured procs. A completely empty vtable will be reported as an error. -The ``vtable`` magic produces types, which can be bound to ``ref`` types and -the ``ptrvtable`` magic produced types bound to ``ptr`` types. +The ``vtref`` magic produces types which can be bound to ``ref`` types and +the ``vtptr`` magic produced types bound to ``ptr`` types. Symbol lookup in generics From 0b0a3e5f203f6b21f3790a6cd50ceeaa8786badc Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Thu, 11 Aug 2016 09:09:15 +0300 Subject: [PATCH 11/37] support derived values in non-generic user type classes --- compiler/ast.nim | 3 +++ compiler/ccgexprs.nim | 4 +-- compiler/semexprs.nim | 39 +++++++++++++++-------------- compiler/sigmatch.nim | 20 +++++---------- compiler/types.nim | 20 ++++++++++----- compiler/vmdeps.nim | 11 +++++--- tests/concepts/tusertypeclasses.nim | 9 +++++-- 7 files changed, 59 insertions(+), 47 deletions(-) diff --git a/compiler/ast.nim b/compiler/ast.nim index bd244fb97..f13691d54 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -428,6 +428,7 @@ const tyAnd, tyOr, tyNot, tyAnything} tyMetaTypes* = {tyGenericParam, tyTypeDesc, tyExpr} + tyTypeClasses + tyUserTypeClasses* = {tyUserTypeClass, tyUserTypeClassInst} type TTypeKinds* = set[TTypeKind] @@ -471,6 +472,8 @@ type # can be attached to generic procs with free standing # type parameters: e.g. proc foo[T]() # depends on unresolved static params. + tfResolved # marks a user type class, after it has been bound to a + # concrete type (lastSon becomes the concrete type) tfRetType, # marks return types in proc (used to detect type classes # used as return types for return type inference) tfCapturesEnv, # whether proc really captures some environment diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 309fb1f20..6e10379e6 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -890,7 +890,7 @@ proc genSeqElem(p: BProc, x, y: PNode, d: var TLoc) = rfmt(nil, "$1->data[$2]", rdLoc(a), rdCharLoc(b)), a.s) proc genBracketExpr(p: BProc; n: PNode; d: var TLoc) = - var ty = skipTypes(n.sons[0].typ, abstractVarRange) + var ty = skipTypes(n.sons[0].typ, abstractVarRange + tyUserTypeClasses) if ty.kind in {tyRef, tyPtr}: ty = skipTypes(ty.lastSon, abstractVarRange) case ty.kind of tyArray: genArrayElem(p, n.sons[0], n.sons[1], d) @@ -1359,7 +1359,7 @@ proc genDollar(p: BProc, n: PNode, d: var TLoc, frmt: string) = proc genArrayLen(p: BProc, e: PNode, d: var TLoc, op: TMagic) = var a = e.sons[1] if a.kind == nkHiddenAddr: a = a.sons[0] - let typ = skipTypes(a.typ, abstractVar) + var typ = skipTypes(a.typ, abstractVar + tyUserTypeClasses) case typ.kind of tyOpenArray, tyVarargs: if op == mHigh: unaryExpr(p, e, d, "($1Len_0-1)") diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 475f496f1..3ec2cd391 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -136,6 +136,7 @@ proc isCastable(dst, src: PType): bool = # castableTypeKinds = {tyInt, tyPtr, tyRef, tyCstring, tyString, # tySequence, tyPointer, tyNil, tyOpenArray, # tyProc, tySet, tyEnum, tyBool, tyChar} + let src = src.skipTypes(tyUserTypeClasses) if skipTypes(dst, abstractInst-{tyOpenArray}).kind == tyOpenArray: return false if skipTypes(src, abstractInst-{tyTypeDesc}).kind == tyTypeDesc: @@ -908,28 +909,14 @@ proc makeDeref(n: PNode): PNode = t = skipTypes(baseTyp, {tyGenericInst, tyAlias}) const - tyTypeParamsHolders = {tyGenericInst, tyUserTypeClassInst, tyCompositeTypeClass} + tyTypeParamsHolders = {tyGenericInst, tyCompositeTypeClass, + tyUserTypeClass, tyUserTypeClassInst} tyDotOpTransparent = {tyVar, tyPtr, tyRef, tyAlias} proc readTypeParameter(c: PContext, typ: PType, paramName: PIdent, info: TLineInfo): PNode = - let ty = if typ.kind in {tyGenericInst, tyUserTypeClassInst}: typ.skipGenericAlias - else: (internalAssert(typ.kind == tyCompositeTypeClass); - typ.sons[1].skipGenericAlias) - - let tbody = ty.sons[0] - for s in countup(0, tbody.len-2): - let tParam = tbody.sons[s] - if tParam.sym.name.id == paramName.id: - let rawTyp = ty.sons[s + 1] - if rawTyp.kind == tyStatic: - return rawTyp.n - else: - let foundTyp = makeTypeDesc(c, rawTyp) - return newSymNode(copySym(tParam.sym).linkTo(foundTyp), info) - - if ty.n != nil: - for statement in ty.n: + if typ.kind in {tyUserTypeClass, tyUserTypeClassInst}: + for statement in typ.n: case statement.kind of nkTypeSection: for def in statement: @@ -939,7 +926,7 @@ proc readTypeParameter(c: PContext, typ: PType, # This seems semantically correct and then we'll be able # to return the section symbol directly here let foundType = makeTypeDesc(c, def[2].typ) - return newSymNode(copySym(def[2].sym).linkTo(foundType), info) + return newSymNode(copySym(def[0].sym).linkTo(foundType), info) of nkConstSection: for def in statement: @@ -948,6 +935,20 @@ proc readTypeParameter(c: PContext, typ: PType, else: discard + + if typ.kind != tyUserTypeClass: + let ty = if typ.kind == tyCompositeTypeClass: typ.sons[1].skipGenericAlias + else: typ.skipGenericAlias + let tbody = ty.sons[0] + for s in countup(0, tbody.len-2): + let tParam = tbody.sons[s] + if tParam.sym.name.id == paramName.id: + let rawTyp = ty.sons[s + 1] + if rawTyp.kind == tyStatic: + return rawTyp.n + else: + let foundTyp = makeTypeDesc(c, rawTyp) + return newSymNode(copySym(tParam.sym).linkTo(foundTyp), info) return nil diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index f33ac76e7..162385e6d 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -1197,22 +1197,14 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = else: return isNone - of tyUserTypeClass: - considerPreviousT: + of tyUserTypeClassInst, tyUserTypeClass: + if f.isResolvedUserTypeClass: + result = typeRel(c, f.lastSon, a) + else: var matched = matchUserTypeClass(c.c, c, f, aOrig) if matched != nil: - # TODO, make user type classes skipable too - put(c, f, a) - result = isGeneric - else: - result = isNone - - of tyUserTypeClassInst: - considerPreviousT: - var matched = matchUserTypeClass(c.c, c, f, aOrig) - if matched != nil: - matched.sons.add a - put(c.bindings, f, matched) + bindConcreteTypeToUserTypeClass(matched, a) + put(c, f, matched) result = isGeneric else: result = isNone diff --git a/compiler/types.nim b/compiler/types.nim index 3e124412b..be7028f9c 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -63,7 +63,7 @@ const abstractVarRange* = {tyGenericInst, tyRange, tyVar, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, tyInferred} abstractInst* = {tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, - tyInferred} + tyInferred} + tyTypeClasses skipPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyTypeDesc, tyAlias, tyInferred} # typedescX is used if we're sure tyTypeDesc should be included (or skipped) @@ -417,6 +417,13 @@ const const preferToResolveSymbols = {preferName, preferModuleInfo, preferGenericArg} +template bindConcreteTypeToUserTypeClass*(tc, concrete: PType) = + tc.sons.safeAdd concrete + tc.flags.incl tfResolved + +template isResolvedUserTypeClass*(t: PType): bool = + tfResolved in t.flags + proc addTypeFlags(name: var string, typ: PType) {.inline.} = if tfNotNil in typ.flags: name.add(" not nil") @@ -461,6 +468,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = if t.n != nil: result.add "(" & renderTree(t.n) & ")" of tyUserTypeClass: internalAssert t.sym != nil and t.sym.owner != nil + if t.isResolvedUserTypeClass: return typeToString(t.lastSon) return t.sym.owner.name.s of tyBuiltInTypeClass: result = case t.base.kind: @@ -1314,12 +1322,15 @@ proc computeSizeAux(typ: PType, a: var BiggestInt): BiggestInt = result = align(result, a) of tyGenericInst, tyDistinct, tyGenericBody, tyAlias: result = computeSizeAux(lastSon(typ), a) + of tyTypeClasses: + result = if typ.isResolvedUserTypeClass: computeSizeAux(typ.lastSon, a) + else: szUnknownSize of tyTypeDesc: result = computeSizeAux(typ.base, a) of tyForward: return szIllegalRecursion of tyStatic: - if typ.n != nil: result = computeSizeAux(lastSon(typ), a) - else: result = szUnknownSize + result = if typ.n != nil: computeSizeAux(typ.lastSon, a) + else: szUnknownSize else: #internalError("computeSizeAux()") result = szUnknownSize @@ -1492,9 +1503,6 @@ proc isEmptyContainer*(t: PType): bool = of tyGenericInst, tyAlias: result = isEmptyContainer(t.lastSon) else: result = false -proc isResolvedUserTypeClass*(t: PType): bool = - t.kind in {tyUserTypeClassInst} and t.base.sonsLen == t.sonsLen - 2 - proc takeType*(formal, arg: PType): PType = # param: openArray[string] = [] # [] is an array constructor of length 0 of type string! diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim index 7094d174b..8c7388643 100644 --- a/compiler/vmdeps.nim +++ b/compiler/vmdeps.nim @@ -175,7 +175,7 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; result.add mapTypeToAst(t.sons[i], info) else: result = mapTypeToAstX(t.lastSon, info, inst, allowRecursion) - of tyGenericBody, tyOrdinal, tyUserTypeClassInst: + of tyGenericBody, tyOrdinal: result = mapTypeToAst(t.lastSon, info) of tyDistinct: if inst: @@ -285,9 +285,12 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; of tyProxy: result = atomicType("error", mNone) of tyBuiltInTypeClass: result = mapTypeToBracket("builtinTypeClass", mNone, t, info) - of tyUserTypeClass: - result = mapTypeToBracket("concept", mNone, t, info) - result.add t.n.copyTree + of tyUserTypeClass, tyUserTypeClassInst: + if t.isResolvedUserTypeClass: + result = mapTypeToAst(t.lastSon, info) + else: + result = mapTypeToBracket("concept", mNone, t, info) + result.add t.n.copyTree of tyCompositeTypeClass: result = mapTypeToBracket("compositeTypeClass", mNone, t, info) of tyAnd: result = mapTypeToBracket("and", mAnd, t, info) diff --git a/tests/concepts/tusertypeclasses.nim b/tests/concepts/tusertypeclasses.nim index 9a20bfae5..e1aaa851c 100644 --- a/tests/concepts/tusertypeclasses.nim +++ b/tests/concepts/tusertypeclasses.nim @@ -2,6 +2,8 @@ discard """ output: '''Sortable Sortable Container +TObj +int ''' """ @@ -20,7 +22,7 @@ type C.len is Ordinal for v in items(C): v.type is tuple|object - + proc foo(c: ObjectContainer) = echo "Container" @@ -79,6 +81,9 @@ proc ptrproc(x: ptr TObj, y: string) = discard proc staticproc(x: static[TObj]) = discard proc typeproc(t: type TObj) = discard -proc testFoo(x: TFoo) = discard +proc testFoo(x: TFoo) = + echo x.TypeName + echo x.MappedType.name + testFoo(TObj(x: 10)) From 0f2c4be1299fc99aeea2011c57240c8cfabd83c3 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 12 Aug 2016 03:25:59 +0300 Subject: [PATCH 12/37] infer static parameters even when more complicated arithmetic is involved --- compiler/ast.nim | 12 +++- compiler/msgs.nim | 2 + compiler/semdata.nim | 15 +---- compiler/semexprs.nim | 1 + compiler/semstmts.nim | 2 +- compiler/semtypes.nim | 1 + compiler/semtypinst.nim | 5 +- compiler/sigmatch.nim | 143 ++++++++++++++++++++++++++++++++-------- compiler/types.nim | 2 +- doc/manual/generics.txt | 4 +- 10 files changed, 139 insertions(+), 48 deletions(-) diff --git a/compiler/ast.nim b/compiler/ast.nim index f13691d54..5adac92df 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -1581,7 +1581,7 @@ proc hasPattern*(s: PSym): bool {.inline.} = result = isRoutine(s) and s.ast.sons[patternPos].kind != nkEmpty iterator items*(n: PNode): PNode = - for i in 0.. 1 and t.n[1].kind == nkStaticExpr - -proc findUnresolvedStaticInRange*(t: PType): (PType, int) = - assert t.kind == tyRange - # XXX: This really needs to become more sophisticated - let upperBound = t.n[1] - if upperBound[0].kind == nkCall: - return (upperBound[0][1].typ, 1) - else: - return (upperBound.typ, 0) + tfUnresolved in t.flags proc errorType*(c: PContext): PType = ## creates a type representing an error state diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 3ec2cd391..930c84368 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -627,6 +627,7 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode = proc semStaticExpr(c: PContext, n: PNode): PNode = let a = semExpr(c, n.sons[0]) + if a.findUnresolvedStatic != nil: return a result = evalStaticExpr(c.module, c.cache, a, c.p.owner) if result.isNil: localError(n.info, errCannotInterpretNodeX, renderTree(n)) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 64449dda4..09631c793 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1621,7 +1621,7 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = elif expr[2].typ.isUnresolvedStatic: inferConceptStaticParam(c, expr[2].typ, expr[1]) continue - + let verdict = semConstExpr(c, n[i]) if verdict.intVal == 0: localError(result.info, "type class predicate failed") diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index bf6c24310..eef83c2a7 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -195,6 +195,7 @@ proc semRangeAux(c: PContext, n: PNode, prev: PType): PType = for i in 0..1: if hasGenericArguments(range[i]): result.n.addSon makeStaticExpr(c, range[i]) + result.flags.incl tfUnresolved else: result.n.addSon semConstExpr(c, range[i]) diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 7e114afb8..9e72e46f6 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -445,7 +445,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = elif t.sons[0].kind != tyNone: result = makeTypeDesc(cl.c, replaceTypeVarsT(cl, t.sons[0])) - of tyUserTypeClass: + of tyUserTypeClass, tyStatic: result = t of tyGenericInst, tyUserTypeClassInst: @@ -502,8 +502,9 @@ proc initTypeVars*(p: PContext, pt: TIdTable, info: TLineInfo; result.owner = owner proc replaceTypesInBody*(p: PContext, pt: TIdTable, n: PNode; - owner: PSym): PNode = + owner: PSym, allowMetaTypes = false): PNode = var cl = initTypeVars(p, pt, n.info, owner) + cl.allowMetaTypes = allowMetaTypes pushInfoContext(n.info) result = replaceTypeVarsN(cl, n) popInfoContext() diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 162385e6d..ca9cdcaf8 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -681,16 +681,125 @@ proc maybeSkipDistinct(t: PType, callee: PSym): PType = else: result = t -proc tryResolvingStaticExpr(c: var TCandidate, n: PNode): PNode = +proc tryResolvingStaticExpr(c: var TCandidate, n: PNode, + allowUnresolved = false): PNode = # Consider this example: # type Value[N: static[int]] = object # proc foo[N](a: Value[N], r: range[0..(N-1)]) # Here, N-1 will be initially nkStaticExpr that can be evaluated only after # N is bound to a concrete value during the matching of the first param. # This proc is used to evaluate such static expressions. - let instantiated = replaceTypesInBody(c.c, c.bindings, n, nil) + let instantiated = replaceTypesInBody(c.c, c.bindings, n, nil, + allowMetaTypes = allowUnresolved) result = c.c.semExpr(c.c, instantiated) +proc inferStaticParam*(lhs: PNode, rhs: BiggestInt): PType = + # This is a simple integer arithimetic equation solver, + # capable of deriving the value of a static parameter in + # expressions such as (N + 5) / 2 = rhs + # + # Preconditions: + # + # * The input of this proc must be semantized + # - all templates should be expanded + # - aby constant folding possible should already be performed + # + # * There must be exactly one unresolved static parameter + # + # Result: + # + # The proc will return the inferred static type with the `n` field + # populated with the inferred value. + # + # `nil` will be returned if the inference was not possible + # + if lhs.kind in nkCallKinds and lhs[0].kind == nkSym: + case lhs[0].sym.magic + of mUnaryLt: + return inferStaticParam(lhs[1], rhs + 1) + + of mAddI, mAddU, mInc, mSucc: + if lhs[1].kind == nkIntLit: + return inferStaticParam(lhs[2], rhs - lhs[1].intVal) + elif lhs[2].kind == nkIntLit: + return inferStaticParam(lhs[1], rhs - lhs[2].intVal) + + of mDec, mSubI, mSubU, mPred: + if lhs[1].kind == nkIntLit: + return inferStaticParam(lhs[2], lhs[1].intVal - rhs) + elif lhs[2].kind == nkIntLit: + return inferStaticParam(lhs[1], rhs + lhs[2].intVal) + + of mMulI, mMulU: + if lhs[1].kind == nkIntLit: + if rhs mod lhs[1].intVal == 0: + return inferStaticParam(lhs[2], rhs div lhs[1].intVal) + elif lhs[2].kind == nkIntLit: + if rhs mod lhs[2].intVal == 0: + return inferStaticParam(lhs[1], rhs div lhs[2].intVal) + + of mDivI, mDivU: + if lhs[1].kind == nkIntLit: + if lhs[1].intVal mod rhs == 0: + return inferStaticParam(lhs[2], lhs[1].intVal div rhs) + elif lhs[2].kind == nkIntLit: + return inferStaticParam(lhs[1], lhs[2].intVal * rhs) + + of mShlI: + if lhs[2].kind == nkIntLit: + return inferStaticParam(lhs[1], rhs shr lhs[2].intVal) + + of mShrI: + if lhs[2].kind == nkIntLit: + return inferStaticParam(lhs[1], rhs shl lhs[2].intVal) + + of mUnaryMinusI: + return inferStaticParam(lhs[1], -rhs) + + of mUnaryPlusI, mToInt, mToBiggestInt: + return inferStaticParam(lhs[1], rhs) + + else: discard + + elif lhs.kind == nkSym and lhs.typ.kind == tyStatic and lhs.typ.n == nil: + lhs.typ.n = newIntNode(nkIntLit, rhs) + return lhs.typ + + return nil + +proc failureToInferStaticParam(n: PNode) = + let staticParam = n.findUnresolvedStatic + let name = if staticParam != nil: staticParam.sym.name.s + else: "unknown" + localError(n.info, errCannotInferStaticParam, name) + +proc inferStaticsInRange(c: var TCandidate, + inferred, concrete: PType): TTypeRelation = + let lowerBound = tryResolvingStaticExpr(c, inferred.n[0], + allowUnresolved = true) + let upperBound = tryResolvingStaticExpr(c, inferred.n[1], + allowUnresolved = true) + + template doInferStatic(c: var TCandidate, e: PNode, r: BiggestInt) = + var exp = e + var rhs = r + var inferred = inferStaticParam(exp, rhs) + if inferred != nil: + put(c.bindings, inferred, inferred) + return isGeneric + else: + failureToInferStaticParam exp + + if lowerBound.kind == nkIntLit: + if upperBound.kind == nkIntLit: + if lengthOrd(concrete) == upperBound.intVal - lowerBound.intVal + 1: + return isGeneric + else: + return isNone + doInferStatic(c, upperBound, lengthOrd(concrete) + lowerBound.intVal - 1) + elif upperBound.kind == nkIntLit: + doInferStatic(c, lowerBound, upperBound.intVal + 1 - lengthOrd(concrete)) + template subtypeCheck() = if result <= isSubrange and f.lastSon.skipTypes(abstractInst).kind in {tyRef, tyPtr, tyVar}: result = isNone @@ -894,34 +1003,10 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = a.sons[1].skipTypes({tyTypeDesc})) if result < isGeneric: return isNone - proc inferStaticRange(c: var TCandidate, inferred, concrete: PType) = - var (staticT, offset) = inferred.findUnresolvedStaticInRange - var - replacementT = newTypeWithSons(c.c, tyStatic, @[tyInt.getSysType]) - concreteUpperBound = concrete.n[1].intVal - # we must correct for the off-by-one discrepancy between - # ranges and static params: - replacementT.n = newIntNode(nkIntLit, concreteUpperBound + offset) - if tfInferrableStatic in staticT.flags: - staticT.n = replacementT.n - put(c.bindings, staticT, replacementT) - - if rangeHasUnresolvedStatic(fRange): - if tfUnresolved in fRange.flags: - # This is a range from an array instantiated with a generic - # static param. We must extract the static param here and bind - # it to the size of the currently supplied array. - inferStaticRange(c, fRange, aRange) - return isGeneric - - let len = tryResolvingStaticExpr(c, fRange.n[1]) - if len.kind == nkIntLit and len.intVal+1 == lengthOrd(a): - return # if we get this far, the result is already good - else: - return isNone + if fRange.rangeHasUnresolvedStatic: + return inferStaticsInRange(c, fRange, a) elif c.c.inTypeClass > 0 and aRange.rangeHasUnresolvedStatic: - inferStaticRange(c, aRange, fRange) - return isGeneric + return inferStaticsInRange(c, aRange, f) elif lengthOrd(fRange) != lengthOrd(a): result = isNone else: discard diff --git a/compiler/types.nim b/compiler/types.nim index be7028f9c..65eb6de61 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -63,7 +63,7 @@ const abstractVarRange* = {tyGenericInst, tyRange, tyVar, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, tyInferred} abstractInst* = {tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, - tyInferred} + tyTypeClasses + tyInferred} skipPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyTypeDesc, tyAlias, tyInferred} # typedescX is used if we're sure tyTypeDesc should be included (or skipped) diff --git a/doc/manual/generics.txt b/doc/manual/generics.txt index 962daa9c4..f4afa0d11 100644 --- a/doc/manual/generics.txt +++ b/doc/manual/generics.txt @@ -367,8 +367,8 @@ operator and also when types dependent on them are being matched: MyConcept[M, N: static[int]; T] = concept x x.foo(SquareMatrix[N, T]) is array[M, int] -Nim may include a simple linear equation solver in the future to help us -infer static params when arithmetic is involved. +The Nim compiler includes a simple linear equation solver, allowing it to +infer static params in some situations where integer arithmetic is involved. Just like in regular type classes, Nim discriminates between ``bind once`` and ``bind many`` types when matching the concept. You can add the ``distinct`` From 644d645ea774dc667feaf5cf8f45035a328a7b5a Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 12 Aug 2016 16:45:28 +0300 Subject: [PATCH 13/37] implement the special treatment of explicit type params in concepts --- compiler/ast.nim | 2 ++ compiler/semmagic.nim | 2 +- compiler/semtypes.nim | 1 + compiler/sigmatch.nim | 4 +++- tests/concepts/tmatrixconcept.nim | 2 +- tests/concepts/tusertypeclasses.nim | 14 ++++++++++++-- 6 files changed, 20 insertions(+), 5 deletions(-) diff --git a/compiler/ast.nim b/compiler/ast.nim index 5adac92df..78c2a6087 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -494,6 +494,8 @@ type tfGenericTypeParam tfImplicitTypeParam tfInferrableStatic + tfExplicit # for typedescs, marks types explicitly prefixed with the + # `type` operator (e.g. type int) tfWildcard # consider a proc like foo[T, I](x: Type[T, I]) # T and I here can bind to both typedesc and static types # before this is determined, we'll consider them to be a diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 5eed1e702..e4ae60aba 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -24,7 +24,7 @@ proc semTypeOf(c: PContext; n: PNode): PNode = result = newNodeI(nkTypeOfExpr, n.info) let typExpr = semExprWithType(c, n, {efInTypeof}) result.add typExpr - result.typ = makeTypeDesc(c, typExpr.typ.skipTypes({tyTypeDesc})) + result.typ = makeTypeDesc(c, typExpr.typ) type SemAsgnMode = enum asgnNormal, noOverloadedSubscript, noOverloadedAsgn diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index eef83c2a7..082fa8dc1 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1239,6 +1239,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = let typExpr = semExprWithType(c, n.sons[0], {efInTypeof}) fixupTypeOf(c, prev, typExpr) result = typExpr.typ + if result.kind == tyTypeDesc: result.flags.incl tfExplicit of nkPar: if sonsLen(n) == 1: result = semTypeNode(c, n.sons[0], prev) else: diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index ca9cdcaf8..a46f03ff6 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -638,6 +638,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, if modifier != tyNone: dummyName = param[0] dummyType = c.makeTypeWithModifier(modifier, a) + if modifier == tyTypeDesc: dummyType.flags.incl tfExplicit else: dummyName = param dummyType = a @@ -833,7 +834,8 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = var useTypeLoweringRuleInTypeClass = c.c.inTypeClass > 0 and not c.isNoCall and - f.kind != tyTypeDesc + f.kind != tyTypeDesc and + tfExplicit notin aOrig.flags aOrig = if useTypeLoweringRuleInTypeClass: aOrig.skipTypes({tyTypeDesc, tyFieldAccessor}) diff --git a/tests/concepts/tmatrixconcept.nim b/tests/concepts/tmatrixconcept.nim index 4bc002dd4..cbb40f791 100644 --- a/tests/concepts/tmatrixconcept.nim +++ b/tests/concepts/tmatrixconcept.nim @@ -25,7 +25,7 @@ type # more complicated static param inference cases m.data is array[TotalElements, T] - M.foo(array[0..FromFoo, type m[int, 10]]) + m.foo(array[0..FromFoo, type m[int, 10]]) MyMatrix[M, K: static[int]; T] = object data: array[M*K, T] diff --git a/tests/concepts/tusertypeclasses.nim b/tests/concepts/tusertypeclasses.nim index e1aaa851c..533bd528d 100644 --- a/tests/concepts/tusertypeclasses.nim +++ b/tests/concepts/tusertypeclasses.nim @@ -15,6 +15,9 @@ type TObj = object x: int + JSonValue = object + val: string + Sortable = concept x, y (x < y) is bool @@ -49,9 +52,14 @@ type staticproc(static[T]) + typeproc T + T.typeproc typeproc o.type o.type.typeproc + o.to(type string) + o.to(type JsonValue) + refproc(r, intref) varproc(v) p.ptrproc(string) @@ -59,9 +67,9 @@ type typeproc(T) const TypeName = T.name - type MappedType = type(T.y) + type MappedType = type(o.y) - intval T.y + intval y(o) let z = intval(o.y) static: @@ -80,6 +88,8 @@ proc refproc(x: ref TObj, y: ref int) = discard proc ptrproc(x: ptr TObj, y: string) = discard proc staticproc(x: static[TObj]) = discard proc typeproc(t: type TObj) = discard +proc to(x: TObj, t: type string) = discard +proc to(x: TObj, t: type JSonValue) = discard proc testFoo(x: TFoo) = echo x.TypeName From 74a80988d9289e8147a791c4b0939d4287baaff3 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Sun, 14 Aug 2016 02:45:29 +0300 Subject: [PATCH 14/37] proper error reporting for concepts and the introduction of the {.explain.} pragma --- compiler/ast.nim | 1 + compiler/msgs.nim | 15 ++- compiler/parser.nim | 8 ++ compiler/pragmas.nim | 6 +- compiler/semcall.nim | 219 ++++++++++++++++++------------------ compiler/semdata.nim | 9 +- compiler/semexprs.nim | 22 +++- compiler/semtypes.nim | 1 + compiler/semtypinst.nim | 2 +- compiler/sigmatch.nim | 58 +++++++--- compiler/wordrecg.nim | 4 +- tests/concepts/texplain.nim | 92 +++++++++++++++ tests/testament/tester.nim | 4 + 13 files changed, 302 insertions(+), 139 deletions(-) create mode 100644 tests/concepts/texplain.nim diff --git a/compiler/ast.nim b/compiler/ast.nim index 78c2a6087..49ca1c5e0 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -252,6 +252,7 @@ type sfProcvar, # proc can be passed to a proc var sfDiscriminant, # field is a discriminant in a record/object sfDeprecated, # symbol is deprecated + sfExplain, # provide more diagnostics when this symbol is used sfError, # usage of symbol should trigger a compile-time error sfShadowed, # a symbol that was shadowed in some inner scope sfThread, # proc will run as a thread diff --git a/compiler/msgs.nim b/compiler/msgs.nim index eb9986114..8f7c43312 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -499,7 +499,6 @@ type TErrorOutput* = enum eStdOut eStdErr - eInMemory TErrorOutputs* = set[TErrorOutput] @@ -653,6 +652,15 @@ var writelnHook*: proc (output: string) {.closure.} structuredErrorHook*: proc (info: TLineInfo; msg: string; severity: Severity) {.closure.} +proc concat(strings: openarray[string]): string = + var totalLen = 0 + for s in strings: totalLen += s.len + result = newStringOfCap totalLen + for s in strings: result.add s + +template writeBufferedMsg(args: varargs[string, `$`]) = + bufferedMsgs.safeAdd concat(args) + proc suggestWriteln*(s: string) = if eStdOut in errorOutputs: if isNil(writelnHook): @@ -806,10 +814,7 @@ macro callStyledWriteLineStderr(args: varargs[typed]): untyped = result.add(arg) template callWritelnHook(args: varargs[string, `$`]) = - var s = "" - for arg in args: - s.add arg - writelnHook s + writelnHook concat(args) template styledMsgWriteln*(args: varargs[typed]) = if not isNil(writelnHook): diff --git a/compiler/parser.nim b/compiler/parser.nim index b63bab781..8457adac7 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -66,6 +66,7 @@ proc parseSymbol*(p: var TParser, allowNil = false): PNode proc parseTry(p: var TParser; isExpr: bool): PNode proc parseCase(p: var TParser): PNode proc parseStmtPragma(p: var TParser): PNode +proc parsePragma(p: var TParser): PNode # implementation proc getTok(p: var TParser) = @@ -770,6 +771,13 @@ proc parseOperators(p: var TParser, headNode: PNode, proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode = result = primary(p, mode) + if p.tok.tokType == tkCurlyDotLe and + p.lex.lineNumber == result.info.line and + mode == pmNormal: + var pragmaExp = newNodeP(nkPragmaExpr, p) + pragmaExp.addSon result + pragmaExp.addSon p.parsePragma + result = pragmaExp result = parseOperators(p, result, limit, mode) proc simpleExpr(p: var TParser, mode = pmNormal): PNode = diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 387738f6d..b30b94b5d 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -55,7 +55,7 @@ const wPure, wHeader, wCompilerproc, wFinal, wSize, wExtern, wShallow, wImportCpp, wImportObjC, wError, wIncompleteStruct, wByCopy, wByRef, wInheritable, wGensym, wInject, wRequiresInit, wUnchecked, wUnion, wPacked, - wBorrow, wGcSafe, wExportNims, wPartial, wUsed} + wBorrow, wGcSafe, wExportNims, wPartial, wUsed, wExplain} fieldPragmas* = {wImportc, wExportc, wDeprecated, wExtern, wImportCpp, wImportObjC, wError, wGuard, wBitsize, wUsed} varPragmas* = {wImportc, wExportc, wVolatile, wRegister, wThreadVar, wNodecl, @@ -73,7 +73,7 @@ const proc pragma*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) # implementation -proc invalidPragma(n: PNode) = +proc invalidPragma*(n: PNode) = localError(n.info, errInvalidPragmaX, renderTree(n, {renderNoComments})) proc pragmaAsm*(c: PContext, n: PNode): char = @@ -773,6 +773,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, of wProcVar: noVal(it) incl(sym.flags, sfProcvar) + of wExplain: + sym.flags.incl sfExplain of wDeprecated: if it.kind == nkExprColonExpr: deprecatedStmt(c, it) elif sym != nil: incl(sym.flags, sfDeprecated) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index b440d1e75..291cf0c6d 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -55,76 +55,60 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, initialBinding: PNode, filter: TSymKinds, best, alt: var TCandidate, - errors: var CandidateErrors) = + errors: var CandidateErrors, + diagnostics = false) = var o: TOverloadIter - var sym = initOverloadIter(o, c, headSymbol) - var scope = o.lastOverloadScope - # Thanks to the lazy semchecking for operands, we need to check whether - # 'initCandidate' modifies the symbol table (via semExpr). - # This can occur in cases like 'init(a, 1, (var b = new(Type2); b))' - let counterInitial = c.currentScope.symbols.counter - var syms: seq[tuple[s: PSym, scope: int]] - var nextSymIndex = 0 - while sym != nil: - if sym.kind in filter: - # Initialise 'best' and 'alt' with the first available symbol - initCandidate(c, best, sym, initialBinding, scope) - initCandidate(c, alt, sym, initialBinding, scope) - best.state = csNoMatch - break - else: - sym = nextOverloadIter(o, c, headSymbol) - scope = o.lastOverloadScope + # thanks to the lazy semchecking for operands, we need to iterate over the + # symbol table *before* any call to 'initCandidate' which might invoke + # semExpr which might modify the symbol table in cases like + # 'init(a, 1, (var b = new(Type2); b))'. + var symx = initOverloadIter(o, c, headSymbol) + let symScope = o.lastOverloadScope + + var syms: seq[tuple[a: PSym, b: int]] = @[] + while symx != nil: + if symx.kind in filter: + syms.add((symx, o.lastOverloadScope)) + symx = nextOverloadIter(o, c, headSymbol) + if syms.len == 0: return var z: TCandidate - while sym != nil: - if sym.kind notin filter: - sym = nextOverloadIter(o, c, headSymbol) - scope = o.lastOverloadScope - continue + initCandidate(c, best, syms[0][0], initialBinding, + symScope, diagnostics = diagnostics) + initCandidate(c, alt, syms[0][0], initialBinding, + symScope, diagnostics = diagnostics) + best.state = csNoMatch + + for i in 0 .. 0: - # fail fast: - globalError(n.info, errTypeMismatch, "") - if errors.isNil or errors.len == 0: - localError(n.info, errExprXCannotBeCalled, n[0].renderTree) - return + initCandidate(c, z, sym, initialBinding, + syms[i][1], diagnostics = diagnostics) + #if sym.name.s == "*" and (n.info ?? "temp5.nim") and n.info.line == 140: + # gDebug = true + matches(c, n, orig, z) + if z.state == csMatch: + # little hack so that iterators are preferred over everything else: + if sym.kind == skIterator: inc(z.exactMatches, 200) + case best.state + of csEmpty, csNoMatch: best = z + of csMatch: + var cmp = cmpCandidates(best, z) + if cmp < 0: best = z # x is better than the best so far + elif cmp == 0: alt = z # x is as good as the best so far + else: discard + #if sym.name.s == "cmp" and (n.info ?? "rstgen.nim") and n.info.line == 516: + # echo "Matches ", n.info, " ", typeToString(sym.typ) + # debug sym + # writeMatches(z) + # for i in 1 .. 0: + # fail fast: + globalError(n.info, errTypeMismatch, "") + if errors.isNil or errors.len == 0: + localError(n.info, errExprXCannotBeCalled, n[0].renderTree) + return + + let (prefer, candidates) = presentFailedCandidates(c, n, errors) + var result = msgKindToString(errTypeMismatch) + add(result, describeArgs(c, n, 1, prefer)) + add(result, ')') if candidates != "": add(result, "\n" & msgKindToString(errButExpected) & "\n" & candidates) localError(n.info, errGenerated, result) @@ -172,7 +170,7 @@ proc bracketNotFoundError(c: PContext; n: PNode) = var symx = initOverloadIter(o, c, headSymbol) while symx != nil: if symx.kind in routineKinds: - errors.add((symx, 0)) + errors.add((symx, 0, nil)) symx = nextOverloadIter(o, c, headSymbol) if errors.len == 0: localError(n.info, "could not resolve: " & $n) @@ -180,7 +178,7 @@ proc bracketNotFoundError(c: PContext; n: PNode) = notFoundError(c, n, errors) proc resolveOverloads(c: PContext, n, orig: PNode, - filter: TSymKinds; + filter: TSymKinds, flags: TExprFlags, errors: var CandidateErrors): TCandidate = var initialBinding: PNode var alt: TCandidate @@ -194,7 +192,7 @@ proc resolveOverloads(c: PContext, n, orig: PNode, template pickBest(headSymbol) = pickBestCandidate(c, headSymbol, n, orig, initialBinding, - filter, result, alt, errors) + filter, result, alt, errors, efExplain in flags) pickBest(f) let overloadsState = result.state @@ -212,7 +210,6 @@ proc resolveOverloads(c: PContext, n, orig: PNode, if result.state != csMatch: n.sons.delete(1) orig.sons.delete(1) - excl n.flags, nfExprCall else: return if nfDotField in n.flags: @@ -260,11 +257,6 @@ proc resolveOverloads(c: PContext, n, orig: PNode, # clean up the inserted ops n.sons.delete(2) n.sons[0] = f - - errors = @[] - pickBest(f) - #notFoundError(c, n, errors) - return if alt.state == csMatch and cmpCandidates(result, alt) == 0 and not sameMethodDispatcher(result.calleeSym, alt.calleeSym): @@ -286,7 +278,6 @@ proc resolveOverloads(c: PContext, n, orig: PNode, getProcHeader(result.calleeSym), getProcHeader(alt.calleeSym), args]) - proc instGenericConvertersArg*(c: PContext, a: PNode, x: TCandidate) = if a.kind == nkHiddenCallConv and a.sons[0].kind == nkSym: let s = a.sons[0].sym @@ -367,7 +358,7 @@ proc semResolvedCall(c: PContext, n: PNode, x: TCandidate): PNode = proc canDeref(n: PNode): bool {.inline.} = result = n.len >= 2 and (let t = n[1].typ; - t != nil and t.skipTypes({tyGenericInst, tyAlias}).kind in {tyPtr, tyRef}) + t != nil and t.skipTypes({tyGenericInst}).kind in {tyPtr, tyRef}) proc tryDeref(n: PNode): PNode = result = newNodeI(nkHiddenDeref, n.info) @@ -375,23 +366,40 @@ proc tryDeref(n: PNode): PNode = result.addSon(n) proc semOverloadedCall(c: PContext, n, nOrig: PNode, - filter: TSymKinds): PNode = - var errors: CandidateErrors - - var r = resolveOverloads(c, n, nOrig, filter, errors) - if r.state == csMatch: result = semResolvedCall(c, n, r) + filter: TSymKinds, flags: TExprFlags): PNode = + var errors: CandidateErrors = if efExplain in flags: @[] + else: nil + var r = resolveOverloads(c, n, nOrig, filter, flags, errors) + if r.state == csMatch: + # this may be triggered, when the explain pragma is used + if errors.len > 0: + let (_, candidates) = presentFailedCandidates(c, n, errors) + message(n.info, hintUser, + "Non-matching candidates for " & renderTree(n) & "\n" & + candidates) + result = semResolvedCall(c, n, r) elif experimentalMode(c) and canDeref(n): # try to deref the first argument and then try overloading resolution again: + # + # XXX: why is this here? + # it could be added to the long list of alternatives tried + # inside `resolveOverloads` or it could be moved all the way + # into sigmatch with hidden conversion produced there + # n.sons[1] = n.sons[1].tryDeref - var r = resolveOverloads(c, n, nOrig, filter, errors) + var r = resolveOverloads(c, n, nOrig, filter, flags, errors) if r.state == csMatch: result = semResolvedCall(c, n, r) else: # get rid of the deref again for a better error message: n.sons[1] = n.sons[1].sons[0] notFoundError(c, n, errors) else: - notFoundError(c, n, errors) - # else: result = errorNode(c, n) + if efExplain notin flags: + # repeat the overload resolution, + # this time enabling all the diagnostic output (this should fail again) + discard semOverloadedCall(c, n, nOrig, filter, flags + {efExplain}) + else: + notFoundError(c, n, errors) proc explicitGenericInstError(n: PNode): PNode = localError(n.info, errCannotInstantiateX, renderTree(n)) @@ -406,7 +414,12 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode = let formal = s.ast.sons[genericParamsPos].sons[i-1].typ let arg = n[i].typ let tm = typeRel(m, formal, arg, true) - if tm in {isNone, isConvertible}: return nil + if tm in {isNone, isConvertible}: + if formal.sonsLen > 0 and formal.sons[0].kind != tyNone: + typeMismatch(n, formal.sons[0], arg) + else: + typeMismatch(n, formal, arg) + break var newInst = generateInstance(c, s, m.bindings, n.info) newInst.typ.flags.excl tfUnresolved markUsed(n.info, s, c.graph.usageSym) @@ -428,7 +441,6 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = "; got " & $(n.len-1) & " type(s) but expected " & $expected) return n result = explicitGenericSym(c, n, s) - if result == nil: result = explicitGenericInstError(n) elif a.kind in {nkClosedSymChoice, nkOpenSymChoice}: # choose the generic proc with the proper number of type parameters. # XXX I think this could be improved by reusing sigmatch.paramTypesMatch. @@ -441,12 +453,11 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = # it suffices that the candidate has the proper number of generic # type parameters: if safeLen(candidate.ast.sons[genericParamsPos]) == n.len-1: - let x = explicitGenericSym(c, n, candidate) - if x != nil: result.add(x) + result.add(explicitGenericSym(c, n, candidate)) # get rid of nkClosedSymChoice if not ambiguous: if result.len == 1 and a.kind == nkClosedSymChoice: result = result[0] - elif result.len == 0: result = explicitGenericInstError(n) + # candidateCount != 1: return explicitGenericInstError(n) else: result = explicitGenericInstError(n) @@ -460,7 +471,7 @@ proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym = call.add(newIdentNode(fn.name, fn.info)) for i in 1.. 4: - localError(body.n[3].info, $body.n[3] & " too nested for type matching") + localError(body.info, $body & " too nested for type matching") return nil openScope(c) inc c.inTypeClass - defer: dec c.inTypeClass closeScope(c) @@ -599,7 +611,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, param: PSym template paramSym(kind): untyped = - newSym(kind, typeParamName, body.sym, body.sym.info) + newSym(kind, typeParamName, Concept.sym, Concept.sym.info) case typ.kind of tyStatic: @@ -622,7 +634,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, addDecl(c, param) typeParams.safeAdd((param, typ)) - for param in body.n[0]: + for param in Concept.n[0]: var dummyName: PNode dummyType: PType @@ -645,11 +657,31 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, internalAssert dummyName.kind == nkIdent var dummyParam = newSym(if modifier == tyTypeDesc: skType else: skVar, - dummyName.ident, body.sym, body.sym.info) + dummyName.ident, Concept.sym, Concept.sym.info) dummyParam.typ = dummyType addDecl(c, dummyParam) - var checkedBody = c.semTryExpr(c, body.n[3].copyTree) + var + oldWriteHook: type(writelnHook) + diagnostics: seq[string] + flags: TExprFlags = {} + collectDiagnostics = m.diagnostics != nil or + sfExplain in Concept.sym.flags + + if collectDiagnostics: + oldWriteHook = writelnHook + # XXX: we can't write to m.diagnostics directly, because + # Nim doesn't support capturing var params in closures + diagnostics = @[] + writelnHook = proc (s: string) = diagnostics.add(s) + flags = {efExplain} + + var checkedBody = c.semTryExpr(c, body.copyTree, flags) + + if collectDiagnostics: + writelnHook = oldWriteHook + for msg in diagnostics: m.diagnostics.safeAdd msg + if checkedBody == nil: return nil # The inferrable type params have been identified during the semTryExpr above. diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim index 6072bd64c..98fd912d8 100644 --- a/compiler/wordrecg.nim +++ b/compiler/wordrecg.nim @@ -66,7 +66,7 @@ type wWrite, wGensym, wInject, wDirty, wInheritable, wThreadVar, wEmit, wAsmNoStackFrame, wImplicitStatic, wGlobal, wCodegenDecl, wUnchecked, wGuard, wLocks, - wPartial, + wPartial, wExplain, wAuto, wBool, wCatch, wChar, wClass, wConst_cast, wDefault, wDelete, wDouble, wDynamic_cast, @@ -152,7 +152,7 @@ const "computedgoto", "injectstmt", "experimental", "write", "gensym", "inject", "dirty", "inheritable", "threadvar", "emit", "asmnostackframe", "implicitstatic", "global", "codegendecl", "unchecked", - "guard", "locks", "partial", + "guard", "locks", "partial", "explain", "auto", "bool", "catch", "char", "class", "const_cast", "default", "delete", "double", diff --git a/tests/concepts/texplain.nim b/tests/concepts/texplain.nim new file mode 100644 index 000000000..9b2b1f70d --- /dev/null +++ b/tests/concepts/texplain.nim @@ -0,0 +1,92 @@ +discard """ + cmd: "nim c --verbosity:0 --colors:off $file" + nimout: ''' +tests/concepts/texplain.nim(71, 10) Hint: Non-matching candidates for e(y) +proc e(i: int): int + [User] +tests/concepts/texplain.nim(74, 7) Hint: Non-matching candidates for e(10) +proc e[ExplainedConcept](o: ExplainedConcept): int +tests/concepts/texplain.nim(38, 6) Error: undeclared field: 'foo' +tests/concepts/texplain.nim(38, 6) Error: undeclared field: '.' +tests/concepts/texplain.nim(38, 6) Error: type mismatch: got ( + [User] +tests/concepts/texplain.nim(77, 10) Hint: Non-matching candidates for e(10) +proc e[ExplainedConcept](o: ExplainedConcept): int +tests/concepts/texplain.nim(38, 6) Error: undeclared field: 'foo' +tests/concepts/texplain.nim(38, 6) Error: undeclared field: '.' +tests/concepts/texplain.nim(38, 6) Error: type mismatch: got ( + [User] +tests/concepts/texplain.nim(81, 20) Error: type mismatch: got ( +tests/concepts/texplain.nim(82, 20) Error: type mismatch: got ( +tests/concepts/texplain.nim(83, 20) Hint: Non-matching candidates for r(y) +proc r(i: string): int + [User] +tests/concepts/texplain.nim(91, 2) Error: type mismatch: got (MatchingType) +but expected one of: +proc f[NestedConcept](o: NestedConcept) +tests/concepts/texplain.nim(42, 6) Error: undeclared field: 'foo' +tests/concepts/texplain.nim(42, 6) Error: undeclared field: '.' +tests/concepts/texplain.nim(42, 6) Error: type mismatch: got ( +tests/concepts/texplain.nim(46, 5) Error: type class predicate failed +''' + line: 46 + errormsg: "type class predicate failed" +""" + +type + ExplainedConcept {.explain.} = concept o + o.foo is int + o.bar is string + + RegularConcept = concept o + o.foo is int + o.bar is string + + NestedConcept = concept o + o.foo is RegularConcept + + NonMatchingType = object + foo: int + bar: int + + MatchingType = object + foo: int + bar: string + +proc e(o: ExplainedConcept): int = 1 +proc e(i: int): int = i + +proc r(o: RegularConcept): int = 1 +proc r(i: string): int = 1 + +proc f(o: NestedConcept) = discard + +var n = NonMatchingType(foo: 10, bar: 20) +var y = MatchingType(foo: 10, bar: "bar") + +# no diagnostic here: +discard e(y) + +# explain that e(int) doesn't match +discard e(y) {.explain.} + +# explain that e(ExplainedConcept) doesn't match +echo(e(10) {.explain.}, 20) + +# explain that e(ExplainedConcept) doesn't again +discard e(10) + +static: + # provide diagnostics why the compile block failed + assert(compiles(e(n)) {.explain.} == false) + assert(compiles(r(n)) {.explain.} == false) + assert(compiles(r(y)) {.explain.} == true) + + # these should not produce any output + assert(compiles(r(10)) == false) + assert(compiles(e(10)) == true) + +# finally, provide multiple nested explanations for failed matching +# of regular concepts, even when the explain pragma is not used +f(y) + diff --git a/tests/testament/tester.nim b/tests/testament/tester.nim index d4a161dab..908eba962 100644 --- a/tests/testament/tester.nim +++ b/tests/testament/tester.nim @@ -184,6 +184,8 @@ proc addResult(r: var TResults, test: TTest, proc cmpMsgs(r: var TResults, expected, given: TSpec, test: TTest) = if strip(expected.msg) notin strip(given.msg): r.addResult(test, expected.msg, given.msg, reMsgsDiffer) + elif expected.nimout.len > 0 and expected.nimout.normalize notin given.nimout.normalize: + r.addResult(test, expected.nimout, given.nimout, reMsgsDiffer) elif expected.tfile == "" and extractFilename(expected.file) != extractFilename(given.file) and "internal error:" notin expected.msg: r.addResult(test, expected.file, given.file, reFilesDiffer) @@ -233,6 +235,8 @@ proc nimoutCheck(test: TTest; expectedNimout: string; given: var TSpec) = if exp notin giv: given.err = reMsgsDiffer +proc normalize(s: string): string = s.strip.replace("\C\L", "\L") + proc makeDeterministic(s: string): string = var x = splitLines(s) sort(x, system.cmp) From 9e9b289fc5c3655550f75f7be47908cb81a3f49d Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Wed, 24 Aug 2016 00:07:22 +0300 Subject: [PATCH 15/37] when displaying proc signatures, don't render the implicit generic params --- compiler/renderer.nim | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/compiler/renderer.nim b/compiler/renderer.nim index badcaea66..7d9536625 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -34,6 +34,7 @@ type pendingWhitespace: int comStack*: seq[PNode] # comment stack flags*: TRenderFlags + inGenericParams: bool checkAnon: bool # we're in a context that can contain sfAnon inPragma: int @@ -83,7 +84,7 @@ proc initSrcGen(g: var TSrcGen, renderFlags: TRenderFlags) = g.flags = renderFlags g.pendingNL = -1 g.pendingWhitespace = -1 - g.checkAnon = false + g.inGenericParams = false proc addTok(g: var TSrcGen, kind: TTokType, s: string) = var length = len(g.tokens) @@ -692,14 +693,14 @@ proc gproc(g: var TSrcGen, n: PNode) = if n.sons[patternPos].kind != nkEmpty: gpattern(g, n.sons[patternPos]) - let oldCheckAnon = g.checkAnon - g.checkAnon = true + let oldInGenericParams = g.inGenericParams + g.inGenericParams = true if renderNoBody in g.flags and n[miscPos].kind != nkEmpty and n[miscPos][1].kind != nkEmpty: gsub(g, n[miscPos][1]) else: gsub(g, n.sons[genericParamsPos]) - g.checkAnon = oldCheckAnon + g.inGenericParams = oldInGenericParams gsub(g, n.sons[paramsPos]) gsub(g, n.sons[pragmasPos]) if renderNoBody notin g.flags: @@ -765,7 +766,10 @@ proc gasm(g: var TSrcGen, n: PNode) = gsub(g, n.sons[1]) proc gident(g: var TSrcGen, n: PNode) = - if g.checkAnon and n.kind == nkSym and sfAnon in n.sym.flags: return + if g.inGenericParams and n.kind == nkSym: + if sfAnon in n.sym.flags or + (n.typ != nil and tfImplicitTypeParam in n.typ.flags): return + var t: TTokType var s = atom(n) if (s[0] in lexer.SymChars): @@ -1315,9 +1319,16 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = gcoms(g) gstmts(g, lastSon(n), c) of nkGenericParams: - put(g, tkBracketLe, "[") - gcomma(g, n) - put(g, tkBracketRi, "]") + proc hasExplicitParams(gp: PNode): bool = + for p in gp: + if p.typ == nil or tfImplicitTypeParam notin p.typ.flags: + return true + return false + + if n.hasExplicitParams: + put(g, tkBracketLe, "[") + gcomma(g, n) + put(g, tkBracketRi, "]") of nkFormalParams: put(g, tkParLe, "(") gsemicolon(g, n, 1) From fe48dd1cbec500298f7edeb75f1d6fef8490346c Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Wed, 24 Aug 2016 01:55:45 +0300 Subject: [PATCH 16/37] further improvements to the error messages produced by concepts --- compiler/msgs.nim | 18 ++++---- compiler/sem.nim | 1 - compiler/semcall.nim | 6 +-- compiler/semexprs.nim | 11 ++--- compiler/semstmts.nim | 2 +- compiler/sigmatch.nim | 9 +++- tests/concepts/texplain.nim | 86 ++++++++++++++++++++++++------------- tests/testament/tester.nim | 6 +-- 8 files changed, 85 insertions(+), 54 deletions(-) diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 8f7c43312..3a97f1ed2 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -133,7 +133,7 @@ type hintConditionAlwaysTrue, hintName, hintPattern, hintExecuting, hintLinking, hintDependency, hintSource, hintStackTrace, hintGCStats, - hintUser + hintUser, hintUserRaw const MsgKindToStr*: array[TMsgKind, string] = [ @@ -434,10 +434,11 @@ const hintSource: "$1", hintStackTrace: "$1", hintGCStats: "$1", - hintUser: "$1"] + hintUser: "$1", + hintUserRaw: "$1"] const - WarningsToStr*: array[0..30, string] = ["CannotOpenFile", "OctalEscape", + WarningsToStr* = ["CannotOpenFile", "OctalEscape", "XIsNeverRead", "XmightNotBeenInit", "Deprecated", "ConfigDeprecated", "SmallLshouldNotBeUsed", "UnknownMagic", @@ -449,12 +450,12 @@ const "ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "GcUnsafe2", "Uninit", "GcMem", "Destructor", "LockLevel", "ResultShadowed", "User"] - HintsToStr*: array[0..22, string] = ["Success", "SuccessX", "LineTooLong", + HintsToStr* = ["Success", "SuccessX", "LineTooLong", "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", "ExprAlwaysX", "QuitCalled", "Processing", "CodeBegin", "CodeEnd", "Conf", "Path", "CondTrue", "Name", "Pattern", "Exec", "Link", "Dependency", "Source", "StackTrace", "GCStats", - "User"] + "User", "UserRaw"] const fatalMin* = errUnknown @@ -658,9 +659,6 @@ proc concat(strings: openarray[string]): string = result = newStringOfCap totalLen for s in strings: result.add s -template writeBufferedMsg(args: varargs[string, `$`]) = - bufferedMsgs.safeAdd concat(args) - proc suggestWriteln*(s: string) = if eStdOut in errorOutputs: if isNil(writelnHook): @@ -929,7 +927,7 @@ proc rawMessage*(msg: TMsgKind, args: openArray[string]) = if msg notin gNotes: return title = HintTitle color = HintColor - kind = HintsToStr[ord(msg) - ord(hintMin)] + if msg != hintUserRaw: kind = HintsToStr[ord(msg) - ord(hintMin)] inc(gHintCounter) let s = msgKindToString(msg) % args @@ -997,7 +995,7 @@ proc liMessage(info: TLineInfo, msg: TMsgKind, arg: string, ignoreMsg = optHints notin gOptions or msg notin gNotes title = HintTitle color = HintColor - kind = HintsToStr[ord(msg) - ord(hintMin)] + if msg != hintUserRaw: kind = HintsToStr[ord(msg) - ord(hintMin)] inc(gHintCounter) # NOTE: currently line info line numbers start with 1, # but column numbers start with 0, however most editors expect diff --git a/compiler/sem.nim b/compiler/sem.nim index 6ad77e3fb..57b87e0bb 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -215,7 +215,6 @@ proc paramsTypeCheck(c: PContext, typ: PType) {.inline.} = proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode proc semWhen(c: PContext, n: PNode, semCheck: bool = true): PNode -proc isOpImpl(c: PContext, n: PNode): PNode proc semTemplateExpr(c: PContext, n: PNode, s: PSym, flags: TExprFlags = {}): PNode proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 291cf0c6d..49b4930eb 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -148,7 +148,7 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) = # Gives a detailed error message; this is separated from semOverloadedCall, # as semOverlodedCall is already pretty slow (and we need this information # only in case of an error). - if c.compilesContextId > 0: + if errorOutputs == {}: # fail fast: globalError(n.info, errTypeMismatch, "") if errors.isNil or errors.len == 0: @@ -263,7 +263,7 @@ proc resolveOverloads(c: PContext, n, orig: PNode, internalAssert result.state == csMatch #writeMatches(result) #writeMatches(alt) - if c.compilesContextId > 0: + if errorOutputs == {}: # quick error message for performance of 'compiles' built-in: globalError(n.info, errGenerated, "ambiguous call") elif gErrorCounter == 0: @@ -374,7 +374,7 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode, # this may be triggered, when the explain pragma is used if errors.len > 0: let (_, candidates) = presentFailedCandidates(c, n, errors) - message(n.info, hintUser, + message(n.info, hintUserRaw, "Non-matching candidates for " & renderTree(n) & "\n" & candidates) result = semResolvedCall(c, n, r) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 59b7e7d7f..b5e9e6dc4 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -302,7 +302,7 @@ proc semOf(c: PContext, n: PNode): PNode = n.typ = getSysType(tyBool) result = n -proc isOpImpl(c: PContext, n: PNode): PNode = +proc isOpImpl(c: PContext, n: PNode, flags: TExprFlags): PNode = internalAssert n.sonsLen == 3 and n[1].typ != nil and n[1].typ.kind == tyTypeDesc and n[2].kind in {nkStrLit..nkTripleStrLit, nkType} @@ -324,12 +324,13 @@ proc isOpImpl(c: PContext, n: PNode): PNode = maybeLiftType(t2, c, n.info) var m: TCandidate initCandidate(c, m, t2) + if efExplain in flags: m.diagnostics = @[] let match = typeRel(m, t2, t1) >= isSubtype # isNone result = newIntNode(nkIntLit, ord(match)) result.typ = n.typ -proc semIs(c: PContext, n: PNode): PNode = +proc semIs(c: PContext, n: PNode, flags: TExprFlags): PNode = if sonsLen(n) != 3: localError(n.info, errXExpectsTwoArguments, "is") @@ -349,7 +350,7 @@ proc semIs(c: PContext, n: PNode): PNode = return # BUGFIX: don't evaluate this too early: ``T is void`` - if not n[1].typ.base.containsGenericType: result = isOpImpl(c, n) + if not n[1].typ.base.containsGenericType: result = isOpImpl(c, n, flags) proc semOpAux(c: PContext, n: PNode) = const flags = {efDetermineType} @@ -754,7 +755,7 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode = # This is a proc variable, apply normal overload resolution let m = resolveIndirectCall(c, n, nOrig, t) if m.state != csMatch: - if c.compilesContextId > 0: + if errorOutputs == {}: # speed up error generation: globalError(n.info, errTypeMismatch, "") return emptyNode @@ -1837,7 +1838,7 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = of mLow: result = semLowHigh(c, setMs(n, s), mLow) of mHigh: result = semLowHigh(c, setMs(n, s), mHigh) of mSizeOf: result = semSizeof(c, setMs(n, s)) - of mIs: result = semIs(c, setMs(n, s)) + of mIs: result = semIs(c, setMs(n, s), flags) of mOf: result = semOf(c, setMs(n, s)) of mShallowCopy: result = semShallowCopy(c, n, flags) of mExpandToAst: result = semExpandToAst(c, n, s, flags) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 09631c793..d42eb5433 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1609,7 +1609,7 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = n.typ = n.sons[i].typ return else: - var expr = semExpr(c, n.sons[i]) + var expr = semExpr(c, n.sons[i], flags) n.sons[i] = expr if c.inTypeClass > 0 and expr.typ != nil: case expr.typ.kind diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index c76b08eb9..5a6f4e015 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -664,6 +664,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, var oldWriteHook: type(writelnHook) diagnostics: seq[string] + errorPrefix: string flags: TExprFlags = {} collectDiagnostics = m.diagnostics != nil or sfExplain in Concept.sym.flags @@ -673,9 +674,13 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, # XXX: we can't write to m.diagnostics directly, because # Nim doesn't support capturing var params in closures diagnostics = @[] - writelnHook = proc (s: string) = diagnostics.add(s) flags = {efExplain} - + writelnHook = proc (s: string) = + if errorPrefix == nil: errorPrefix = Concept.sym.name.s & ":" + let msg = s.replace("Error:", errorPrefix) + if oldWriteHook != nil: oldWriteHook msg + diagnostics.add msg + var checkedBody = c.semTryExpr(c, body.copyTree, flags) if collectDiagnostics: diff --git a/tests/concepts/texplain.nim b/tests/concepts/texplain.nim index 9b2b1f70d..186621f5b 100644 --- a/tests/concepts/texplain.nim +++ b/tests/concepts/texplain.nim @@ -1,36 +1,63 @@ discard """ cmd: "nim c --verbosity:0 --colors:off $file" nimout: ''' -tests/concepts/texplain.nim(71, 10) Hint: Non-matching candidates for e(y) +tests/concepts/texplain.nim(99, 10) Hint: Non-matching candidates for e(y) proc e(i: int): int - [User] -tests/concepts/texplain.nim(74, 7) Hint: Non-matching candidates for e(10) -proc e[ExplainedConcept](o: ExplainedConcept): int -tests/concepts/texplain.nim(38, 6) Error: undeclared field: 'foo' -tests/concepts/texplain.nim(38, 6) Error: undeclared field: '.' -tests/concepts/texplain.nim(38, 6) Error: type mismatch: got ( - [User] -tests/concepts/texplain.nim(77, 10) Hint: Non-matching candidates for e(10) -proc e[ExplainedConcept](o: ExplainedConcept): int -tests/concepts/texplain.nim(38, 6) Error: undeclared field: 'foo' -tests/concepts/texplain.nim(38, 6) Error: undeclared field: '.' -tests/concepts/texplain.nim(38, 6) Error: type mismatch: got ( - [User] -tests/concepts/texplain.nim(81, 20) Error: type mismatch: got ( -tests/concepts/texplain.nim(82, 20) Error: type mismatch: got ( -tests/concepts/texplain.nim(83, 20) Hint: Non-matching candidates for r(y) -proc r(i: string): int - [User] -tests/concepts/texplain.nim(91, 2) Error: type mismatch: got (MatchingType) + +tests/concepts/texplain.nim(102, 7) Hint: Non-matching candidates for e(10) +proc e(o: ExplainedConcept): int +tests/concepts/texplain.nim(65, 6) ExplainedConcept: undeclared field: 'foo' +tests/concepts/texplain.nim(65, 6) ExplainedConcept: undeclared field: '.' +tests/concepts/texplain.nim(65, 6) ExplainedConcept: expression '.' cannot be called +tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed +tests/concepts/texplain.nim(66, 6) ExplainedConcept: undeclared field: 'bar' +tests/concepts/texplain.nim(66, 6) ExplainedConcept: undeclared field: '.' +tests/concepts/texplain.nim(66, 6) ExplainedConcept: expression '.' cannot be called +tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed + +tests/concepts/texplain.nim(105, 10) Hint: Non-matching candidates for e(10) +proc e(o: ExplainedConcept): int +tests/concepts/texplain.nim(65, 6) ExplainedConcept: undeclared field: 'foo' +tests/concepts/texplain.nim(65, 6) ExplainedConcept: undeclared field: '.' +tests/concepts/texplain.nim(65, 6) ExplainedConcept: expression '.' cannot be called +tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed +tests/concepts/texplain.nim(66, 6) ExplainedConcept: undeclared field: 'bar' +tests/concepts/texplain.nim(66, 6) ExplainedConcept: undeclared field: '.' +tests/concepts/texplain.nim(66, 6) ExplainedConcept: expression '.' cannot be called +tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed + +tests/concepts/texplain.nim(109, 20) Error: type mismatch: got (NonMatchingType) but expected one of: -proc f[NestedConcept](o: NestedConcept) -tests/concepts/texplain.nim(42, 6) Error: undeclared field: 'foo' -tests/concepts/texplain.nim(42, 6) Error: undeclared field: '.' -tests/concepts/texplain.nim(42, 6) Error: type mismatch: got ( -tests/concepts/texplain.nim(46, 5) Error: type class predicate failed +proc e(o: ExplainedConcept): int +tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed +proc e(i: int): int + +tests/concepts/texplain.nim(110, 20) Error: type mismatch: got (NonMatchingType) +but expected one of: +proc r(o: RegularConcept): int +tests/concepts/texplain.nim(69, 5) RegularConcept: type class predicate failed +proc r[T](a: SomeNumber; b: T; c: auto) +proc r(i: string): int + +tests/concepts/texplain.nim(111, 20) Hint: Non-matching candidates for r(y) +proc r[T](a: SomeNumber; b: T; c: auto) +proc r(i: string): int + +tests/concepts/texplain.nim(119, 2) Error: type mismatch: got (MatchingType) +but expected one of: +proc f(o: NestedConcept) +tests/concepts/texplain.nim(69, 6) RegularConcept: undeclared field: 'foo' +tests/concepts/texplain.nim(69, 6) RegularConcept: undeclared field: '.' +tests/concepts/texplain.nim(69, 6) RegularConcept: expression '.' cannot be called +tests/concepts/texplain.nim(69, 5) RegularConcept: type class predicate failed +tests/concepts/texplain.nim(70, 6) RegularConcept: undeclared field: 'bar' +tests/concepts/texplain.nim(70, 6) RegularConcept: undeclared field: '.' +tests/concepts/texplain.nim(70, 6) RegularConcept: expression '.' cannot be called +tests/concepts/texplain.nim(69, 5) RegularConcept: type class predicate failed +tests/concepts/texplain.nim(73, 5) NestedConcept: type class predicate failed ''' - line: 46 - errormsg: "type class predicate failed" + line: 119 + errormsg: "type mismatch: got (MatchingType)" """ type @@ -56,6 +83,7 @@ type proc e(o: ExplainedConcept): int = 1 proc e(i: int): int = i +proc r[T](a: SomeNumber, b: T, c: auto) = discard proc r(o: RegularConcept): int = 1 proc r(i: string): int = 1 @@ -77,11 +105,11 @@ echo(e(10) {.explain.}, 20) discard e(10) static: - # provide diagnostics why the compile block failed + # provide diagnostics why the compile block failed assert(compiles(e(n)) {.explain.} == false) assert(compiles(r(n)) {.explain.} == false) assert(compiles(r(y)) {.explain.} == true) - + # these should not produce any output assert(compiles(r(10)) == false) assert(compiles(e(10)) == true) diff --git a/tests/testament/tester.nim b/tests/testament/tester.nim index 908eba962..2d758ef0d 100644 --- a/tests/testament/tester.nim +++ b/tests/testament/tester.nim @@ -63,6 +63,8 @@ let var targets = {low(TTarget)..high(TTarget)} +proc normalizeMsg(s: string): string = s.strip.replace("\C\L", "\L") + proc callCompiler(cmdTemplate, filename, options: string, target: TTarget): TSpec = let c = parseCmdLine(cmdTemplate % ["target", targetToCmd[target], @@ -184,7 +186,7 @@ proc addResult(r: var TResults, test: TTest, proc cmpMsgs(r: var TResults, expected, given: TSpec, test: TTest) = if strip(expected.msg) notin strip(given.msg): r.addResult(test, expected.msg, given.msg, reMsgsDiffer) - elif expected.nimout.len > 0 and expected.nimout.normalize notin given.nimout.normalize: + elif expected.nimout.len > 0 and expected.nimout.normalizeMsg notin given.nimout.normalizeMsg: r.addResult(test, expected.nimout, given.nimout, reMsgsDiffer) elif expected.tfile == "" and extractFilename(expected.file) != extractFilename(given.file) and "internal error:" notin expected.msg: @@ -235,8 +237,6 @@ proc nimoutCheck(test: TTest; expectedNimout: string; given: var TSpec) = if exp notin giv: given.err = reMsgsDiffer -proc normalize(s: string): string = s.strip.replace("\C\L", "\L") - proc makeDeterministic(s: string): string = var x = splitLines(s) sort(x, system.cmp) From 52b241fd5703034066d9c9f3c3d514162c2c809e Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Thu, 25 Aug 2016 02:14:25 +0300 Subject: [PATCH 17/37] new type traits: `GenericHead` and `StripGenericParams` --- compiler/semmagic.nim | 37 +++++++++++++++++++++++++++++++++++-- compiler/semtypes.nim | 11 ++++++++++- lib/pure/typetraits.nim | 19 +++++++++++++++++-- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index e4ae60aba..ee6329125 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -86,9 +86,32 @@ proc semInstantiationInfo(c: PContext, n: PNode): PNode = result.add(filename) result.add(line) +proc toNode(t: PType, i: TLineInfo): PNode = + result = newNodeIT(nkType, i, t) + +const + # these are types that use the bracket syntax for instantiation + # they can be subjected to the type traits `GenericHead` and + # `Uninstantiated` + tyUserDefinedGenerics* = {tyGenericInst, tyGenericInvocation, + tyUserTypeClassInst} + + tyMagicGenerics* = {tySet, tySequence, tyArray, tyOpenArray} + + tyGenericLike* = tyUserDefinedGenerics + + tyMagicGenerics + + {tyCompositeTypeClass} + +proc uninstantiate(t: PType): PType = + result = case t.kind + of tyMagicGenerics: t + of tyUserDefinedGenerics: t.base + of tyCompositeTypeClass: uninstantiate t.sons[1] + else: t + proc evalTypeTrait(trait: PNode, operand: PType, context: PSym): PNode = - let typ = operand.skipTypes({tyTypeDesc}) - case trait.sym.name.s.normalize + var typ = operand.skipTypes({tyTypeDesc}) + case trait.sym.name.s of "name": result = newStrNode(nkStrLit, typ.typeToString(preferName)) result.typ = newType(tyString, context) @@ -97,6 +120,16 @@ proc evalTypeTrait(trait: PNode, operand: PType, context: PSym): PNode = result = newIntNode(nkIntLit, typ.len - ord(typ.kind==tyProc)) result.typ = newType(tyInt, context) result.info = trait.info + of "GenericHead": + var res = uninstantiate(typ) + if res == typ and res.kind notin tyMagicGenerics: + localError(trait.info, + "GenericHead expects a generic type. The given type was " & + typeToString(typ)) + return newType(tyError, context).toNode(trait.info) + result = res.base.toNode(trait.info) + of "StripGenericParams": + result = uninstantiate(typ).toNode(trait.info) else: internalAssert false diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 9e0d9b633..204641b74 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1226,6 +1226,13 @@ proc fixupTypeOf(c: PContext, prev: PType, typExpr: PNode) = result.sym = prev.sym assignType(prev, result) +proc symFromExpectedTypeNode(c: PContext, n: PNode): PSym = + if n.kind == nkType: + result = symFromType(n.typ, n.info) + else: + localError(n.info, "xx") + result = errorSym(c, n) + proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = nil when defined(nimsuggest): @@ -1316,7 +1323,9 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = semTypeNode(c, whenResult, prev) of nkBracketExpr: checkMinSonsLen(n, 2) - var s = semTypeIdent(c, n.sons[0]) + var head = n.sons[0] + var s = if head.kind notin nkCallKinds: semTypeIdent(c, head) + else: symFromExpectedTypeNode(c, semExpr(c, head)) case s.magic of mArray: result = semArray(c, n, prev) of mOpenArray: result = semContainer(c, n, tyOpenArray, "openarray", prev) diff --git a/lib/pure/typetraits.nim b/lib/pure/typetraits.nim index 2c3d872df..cc7fb9f72 100644 --- a/lib/pure/typetraits.nim +++ b/lib/pure/typetraits.nim @@ -19,7 +19,7 @@ proc name*(t: typedesc): string {.magic: "TypeTrait".} ## ## import typetraits ## - ## proc `$`*[T](some:typedesc[T]): string = name(T) + ## proc `$`*(T: typedesc): string = name(T) ## ## template test(x): stmt = ## echo "type: ", type(x), ", value: ", x @@ -31,6 +31,21 @@ proc name*(t: typedesc): string {.magic: "TypeTrait".} ## test(@['A','B']) ## # --> type: seq[char], value: @[A, B] - proc arity*(t: typedesc): int {.magic: "TypeTrait".} ## Returns the arity of the given type + +proc GenericHead*(t: typedesc): typedesc {.magic: "TypeTrait".} + ## Accepts an instantiated generic type and returns its + ## uninstantiated form. + ## + ## For example: + ## seq[int].GenericHead will be just seq + ## seq[int].GenericHead[float] will be seq[float] + ## + ## A compile-time error will be produced if the supplied type + ## is not generic + +proc StripGenericParams*(t: typedesc): typedesc {.magic: "TypeTrait".} + ## This trait is similar to `GenericHead`, but instead of producing + ## error for non-generic types, it will just return them unmodified + From cbf66e99a85551021d768aa81858f9d7b6f55d6f Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Wed, 31 Aug 2016 02:17:14 +0300 Subject: [PATCH 18/37] Working test cases for the sophisticated matrix library example from the manual Fixed the dot operator when used within return types (see tgenericdotrettype) Fixed the matching of generic concepts aliases used with the implicit generics style --- compiler/semexprs.nim | 28 +++++++++++----- compiler/semtypes.nim | 10 +++--- compiler/semtypinst.nim | 2 ++ compiler/sigmatch.nim | 48 ++++++++++++++++----------- compiler/types.nim | 18 +++++----- compiler/vmgen.nim | 3 +- doc/manual/generics.txt | 30 ++++++++++++----- tests/concepts/matrix.nim | 15 +++++++++ tests/concepts/matrixalgo.nim | 28 ++++++++++++++++ tests/concepts/tmatrixconcept.nim | 22 +++++++++--- tests/concepts/tmatrixlib.nim | 31 +++++++++++++++++ tests/generics/tgenericdotrettype.nim | 29 ++++++++++++++++ 12 files changed, 211 insertions(+), 53 deletions(-) create mode 100644 tests/concepts/matrix.nim create mode 100644 tests/concepts/matrixalgo.nim create mode 100644 tests/concepts/tmatrixlib.nim create mode 100644 tests/generics/tgenericdotrettype.nim diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index b5e9e6dc4..9d0f4aef1 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -911,8 +911,7 @@ proc makeDeref(n: PNode): PNode = t = skipTypes(baseTyp, {tyGenericInst, tyAlias}) const - tyTypeParamsHolders = {tyGenericInst, tyCompositeTypeClass, - tyUserTypeClass, tyUserTypeClassInst} + tyTypeParamsHolders = {tyGenericInst, tyCompositeTypeClass} tyDotOpTransparent = {tyVar, tyPtr, tyRef, tyAlias} proc readTypeParameter(c: PContext, typ: PType, @@ -1107,6 +1106,23 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = var ty = n.sons[0].typ var f: PSym = nil result = nil + + template tryReadingGenericParam(t: PType) = + case t.kind + of tyTypeParamsHolders: + return readTypeParameter(c, t, i, n.info) + of tyUserTypeClasses: + if t.isResolvedUserTypeClass: + return readTypeParameter(c, t, i, n.info) + else: + n.typ = makeTypeFromExpr(c, copyTree(n)) + return n + of tyGenericParam: + n.typ = makeTypeFromExpr(c, copyTree(n)) + return n + else: + discard + if isTypeExpr(n.sons[0]) or (ty.kind == tyTypeDesc and ty.base.kind != tyNone): if ty.kind == tyTypeDesc: ty = ty.base ty = ty.skipTypes(tyDotOpTransparent) @@ -1124,8 +1140,6 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = markUsed(n.info, f, c.graph.usageSym) styleCheckUse(n.info, f) return - of tyTypeParamsHolders: - return readTypeParameter(c, ty, i, n.info) of tyObject, tyTuple: if ty.n != nil and ty.n.kind == nkRecList: let field = lookupInRecord(ty.n, i) @@ -1134,8 +1148,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = n.typ.n = copyTree(n) return n else: - # echo "TYPE FIELD ACCESS" - # debug ty + tryReadingGenericParam(ty) return # XXX: This is probably not relevant any more # reset to prevent 'nil' bug: see "tests/reject/tenumitems.nim": @@ -1178,8 +1191,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = # we didn't find any field, let's look for a generic param if result == nil: let t = n.sons[0].typ.skipTypes(tyDotOpTransparent) - if t.kind in tyTypeParamsHolders: - result = readTypeParameter(c, t, i, n.info) + tryReadingGenericParam(t) proc dotTransformation(c: PContext, n: PNode): PNode = if isSymChoice(n.sons[1]): diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 204641b74..42ffaa5fa 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -845,9 +845,9 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, for i in 0 .. paramType.sonsLen - 2: if paramType.sons[i].kind == tyStatic: - var x = copyNode(ast.emptyNode) - x.typ = paramType.sons[i] - result.rawAddSon makeTypeFromExpr(c, x) # aka 'tyUnknown' + var staticCopy = paramType.sons[i].exactReplica + staticCopy.flags.incl tfInferrableStatic + result.rawAddSon staticCopy else: result.rawAddSon newTypeS(tyAnything, c) @@ -891,7 +891,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, allowMetaTypes = true) result = liftingWalk(expanded, true) - of tyUserTypeClass, tyBuiltInTypeClass, tyAnd, tyOr, tyNot: + of tyUserTypeClasses, tyBuiltInTypeClass, tyAnd, tyOr, tyNot: result = addImplicitGeneric(copyType(paramType, getCurrOwner(c), true)) of tyGenericParam: @@ -1357,6 +1357,8 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = else: result = semGeneric(c, n, s, prev) of nkDotExpr: let typeExpr = semExpr(c, n) + if typeExpr.typ.kind == tyFromExpr: + return typeExpr.typ if typeExpr.typ.kind != tyTypeDesc: localError(n.info, errTypeExpected) result = errorType(c) diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index ac8a5ff82..fddcc7a24 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -404,6 +404,8 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = case t.kind of tyGenericInvocation: result = handleGenericInvocation(cl, t) + if result.lastSon.kind == tyUserTypeClass: + result.kind = tyUserTypeClassInst of tyGenericBody: localError(cl.info, errCannotInstantiateX, typeToString(t)) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 5a6f4e015..a40f8ee66 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -613,26 +613,34 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, template paramSym(kind): untyped = newSym(kind, typeParamName, Concept.sym, Concept.sym.info) - case typ.kind - of tyStatic: - param = paramSym skConst - param.typ = typ.exactReplica - if typ.n == nil: - param.typ.flags.incl tfInferrableStatic - else: - param.ast = typ.n - of tyUnknown: - param = paramSym skVar - param.typ = typ.exactReplica - else: - param = paramSym skType - param.typ = if typ.isMetaType: - c.newTypeWithSons(tyInferred, @[typ]) - else: - makeTypeDesc(c, typ) + block addTypeParam: + for prev in typeParams: + if prev[1].id == typ.id: + param = paramSym prev[0].kind + param.typ = prev[0].typ + break addTypeParam + case typ.kind + of tyStatic: + param = paramSym skConst + param.typ = typ.exactReplica + if typ.n == nil: + param.typ.flags.incl tfInferrableStatic + else: + param.ast = typ.n + of tyUnknown: + param = paramSym skVar + param.typ = typ.exactReplica + else: + param = paramSym skType + param.typ = if typ.isMetaType: + c.newTypeWithSons(tyInferred, @[typ]) + else: + makeTypeDesc(c, typ) + + typeParams.safeAdd((param, typ)) + addDecl(c, param) - typeParams.safeAdd((param, typ)) for param in Concept.n[0]: var @@ -682,7 +690,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, diagnostics.add msg var checkedBody = c.semTryExpr(c, body.copyTree, flags) - + if collectDiagnostics: writelnHook = oldWriteHook for msg in diagnostics: m.diagnostics.safeAdd msg @@ -696,7 +704,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, put(m.bindings, p[1], p[0].typ) if ff.kind == tyUserTypeClassInst: - result = generateTypeInstance(c, m.bindings, ff.sym.info, ff) + result = generateTypeInstance(c, m.bindings, Concept.sym.info, ff) else: result = copyType(ff, ff.owner, true) diff --git a/compiler/types.nim b/compiler/types.nim index 65eb6de61..3f84548a1 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -183,7 +183,7 @@ proc iterOverTypeAux(marker: var IntSet, t: PType, iter: TTypeIter, if result: return if not containsOrIncl(marker, t.id): case t.kind - of tyGenericInst, tyGenericBody, tyAlias: + of tyGenericInst, tyGenericBody, tyAlias, tyInferred: result = iterOverTypeAux(marker, lastSon(t), iter, closure) else: for i in countup(0, sonsLen(t) - 1): @@ -1320,6 +1320,9 @@ proc computeSizeAux(typ: PType, a: var BiggestInt): BiggestInt = if result < 0: return if a < maxAlign: a = maxAlign result = align(result, a) + of tyInferred: + if typ.len > 1: + result = computeSizeAux(typ.lastSon, a) of tyGenericInst, tyDistinct, tyGenericBody, tyAlias: result = computeSizeAux(lastSon(typ), a) of tyTypeClasses: @@ -1351,18 +1354,17 @@ proc getSize(typ: PType): BiggestInt = if result < 0: internalError("getSize: " & $typ.kind) proc containsGenericTypeIter(t: PType, closure: RootRef): bool = - if t.kind == tyStatic: + case t.kind + of tyStatic: return t.n == nil - - if t.kind == tyTypeDesc: + of tyTypeDesc: if t.base.kind == tyNone: return true if containsGenericTypeIter(t.base, closure): return true return false - - if t.kind in GenericTypes + tyTypeClasses + {tyFromExpr}: + of GenericTypes + tyTypeClasses + {tyFromExpr}: return true - - return false + else: + return false proc containsGenericType*(t: PType): bool = result = iterOverType(t, containsGenericTypeIter, nil) diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 125fe8ae0..c7d9be48c 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -1678,7 +1678,8 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) = elif sfImportc in s.flags: c.importcSym(n.info, s) genLit(c, n, dest) of skConst: - gen(c, s.ast, dest) + let constVal = if s.ast != nil: s.ast else: s.typ.n + gen(c, constVal, dest) of skEnumField: if dest < 0: dest = c.getTemp(n.typ) if s.position >= low(int16) and s.position <= high(int16): diff --git a/doc/manual/generics.txt b/doc/manual/generics.txt index f4afa0d11..d1af6d962 100644 --- a/doc/manual/generics.txt +++ b/doc/manual/generics.txt @@ -305,6 +305,9 @@ The concept types can be parametric just like the regular generic types: .. code-block:: nim ### matrixalgo.nim + + import typetraits + type AnyMatrix*[R, C: static[int]; T] = concept m, var mvar, type M M.ValueType is T @@ -314,9 +317,13 @@ The concept types can be parametric just like the regular generic types: m[int, int] is T mvar[int, int] = T + type TransposedType = StripGenericParams(M)[C, R, T] + AnySquareMatrix*[N: static[int], T] = AnyMatrix[N, N, T] - proc transpose*[R, C, T](m: AnyMatrix[R, C, T]): m.type.basis[C, R, T] = + AnyTransform3D* = AnyMatrix[4, 4, float] + + proc transposed*(m: AnyMatrix): m.TransposedType = for r in 0 .. Date: Fri, 27 Jan 2017 00:22:02 +0200 Subject: [PATCH 19/37] carefully reviewed and merged the changes from the devel branch --- compiler/semcall.nim | 136 ++++++++++++++++++++++++------------------- 1 file changed, 75 insertions(+), 61 deletions(-) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 49b4930eb..e1f09c3d5 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -35,10 +35,11 @@ proc sameMethodDispatcher(a, b: PSym): bool = proc determineType(c: PContext, s: PSym) proc initCandidateSymbols(c: PContext, headSymbol: PNode, - initialBinding: PNode, - filter: TSymKinds, - best, alt: var TCandidate, - o: var TOverloadIter): seq[tuple[s: PSym, scope: int]] = + initialBinding: PNode, + filter: TSymKinds, + best, alt: var TCandidate, + o: var TOverloadIter, + diagnostics: bool): seq[tuple[s: PSym, scope: int]] = result = @[] var symx = initOverloadIter(o, c, headSymbol) while symx != nil: @@ -46,8 +47,10 @@ proc initCandidateSymbols(c: PContext, headSymbol: PNode, result.add((symx, o.lastOverloadScope)) symx = nextOverloadIter(o, c, headSymbol) if result.len > 0: - initCandidate(c, best, result[0].s, initialBinding, result[0].scope) - initCandidate(c, alt, result[0].s, initialBinding, result[0].scope) + initCandidate(c, best, result[0].s, initialBinding, + result[0].scope, diagnostics) + initCandidate(c, alt, result[0].s, initialBinding, + result[0].scope, diagnostics) best.state = csNoMatch proc pickBestCandidate(c: PContext, headSymbol: PNode, @@ -56,55 +59,62 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, filter: TSymKinds, best, alt: var TCandidate, errors: var CandidateErrors, - diagnostics = false) = + diagnosticsFlag = false) = var o: TOverloadIter - # thanks to the lazy semchecking for operands, we need to iterate over the - # symbol table *before* any call to 'initCandidate' which might invoke - # semExpr which might modify the symbol table in cases like - # 'init(a, 1, (var b = new(Type2); b))'. - var symx = initOverloadIter(o, c, headSymbol) - let symScope = o.lastOverloadScope - - var syms: seq[tuple[a: PSym, b: int]] = @[] - while symx != nil: - if symx.kind in filter: - syms.add((symx, o.lastOverloadScope)) - symx = nextOverloadIter(o, c, headSymbol) - if syms.len == 0: return + var sym = initOverloadIter(o, c, headSymbol) + var scope = o.lastOverloadScope + # Thanks to the lazy semchecking for operands, we need to check whether + # 'initCandidate' modifies the symbol table (via semExpr). + # This can occur in cases like 'init(a, 1, (var b = new(Type2); b))' + let counterInitial = c.currentScope.symbols.counter + var syms: seq[tuple[s: PSym, scope: int]] + var nextSymIndex = 0 + while sym != nil: + if sym.kind in filter: + # Initialise 'best' and 'alt' with the first available symbol + initCandidate(c, best, sym, initialBinding, scope, diagnosticsFlag) + initCandidate(c, alt, sym, initialBinding, scope, diagnosticsFlag) + best.state = csNoMatch + break + else: + sym = nextOverloadIter(o, c, headSymbol) + scope = o.lastOverloadScope var z: TCandidate - initCandidate(c, best, syms[0][0], initialBinding, - symScope, diagnostics = diagnostics) - initCandidate(c, alt, syms[0][0], initialBinding, - symScope, diagnostics = diagnostics) - best.state = csNoMatch - - for i in 0 .. = 2 and (let t = n[1].typ; - t != nil and t.skipTypes({tyGenericInst}).kind in {tyPtr, tyRef}) + t != nil and t.skipTypes({tyGenericInst, tyAlias}).kind in {tyPtr, tyRef}) proc tryDeref(n: PNode): PNode = result = newNodeI(nkHiddenDeref, n.info) @@ -414,12 +425,7 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode = let formal = s.ast.sons[genericParamsPos].sons[i-1].typ let arg = n[i].typ let tm = typeRel(m, formal, arg, true) - if tm in {isNone, isConvertible}: - if formal.sonsLen > 0 and formal.sons[0].kind != tyNone: - typeMismatch(n, formal.sons[0], arg) - else: - typeMismatch(n, formal, arg) - break + if tm in {isNone, isConvertible}: return nil var newInst = generateInstance(c, s, m.bindings, n.info) newInst.typ.flags.excl tfUnresolved markUsed(n.info, s, c.graph.usageSym) @@ -441,6 +447,7 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = "; got " & $(n.len-1) & " type(s) but expected " & $expected) return n result = explicitGenericSym(c, n, s) + if result == nil: result = explicitGenericInstError(n) elif a.kind in {nkClosedSymChoice, nkOpenSymChoice}: # choose the generic proc with the proper number of type parameters. # XXX I think this could be improved by reusing sigmatch.paramTypesMatch. @@ -453,10 +460,12 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = # it suffices that the candidate has the proper number of generic # type parameters: if safeLen(candidate.ast.sons[genericParamsPos]) == n.len-1: - result.add(explicitGenericSym(c, n, candidate)) + let x = explicitGenericSym(c, n, candidate) + if x != nil: result.add(x) # get rid of nkClosedSymChoice if not ambiguous: if result.len == 1 and a.kind == nkClosedSymChoice: result = result[0] + elif result.len == 0: result = explicitGenericInstError(n) # candidateCount != 1: return explicitGenericInstError(n) else: result = explicitGenericInstError(n) @@ -471,7 +480,7 @@ proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym = call.add(newIdentNode(fn.name, fn.info)) for i in 1.. Date: Fri, 27 Jan 2017 01:17:34 +0200 Subject: [PATCH 20/37] restore compilation and make all concepts tests green --- compiler/ccgtypes.nim | 3 +- compiler/options.nim | 4 +-- compiler/sigmatch.nim | 8 ++--- tests/concepts/texplain.nim | 68 ++++++++++++++++++------------------- 4 files changed, 42 insertions(+), 41 deletions(-) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index d62eab8ac..9915ad355 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -790,7 +790,8 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = of 1, 2, 4, 8: addf(m.s[cfsTypes], "typedef NU$2 $1;$n", [result, rope(s*8)]) else: addf(m.s[cfsTypes], "typedef NU8 $1[$2];$n", [result, rope(getSize(t))]) - of tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias: + of tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, + tyUserTypeClass, tyUserTypeClassInst, tyInferred: result = getTypeDescAux(m, lastSon(t), check) else: internalError("getTypeDescAux(" & $t.kind & ')') diff --git a/compiler/options.nim b/compiler/options.nim index 063dff826..c4a57f41c 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -72,8 +72,8 @@ type # please make sure we have under 32 options TGlobalOptions* = set[TGlobalOption] const - harmlessOptions* = {optForceFullMake, optNoLinking, optReportConceptFailures, - optRun, optUseColors, optStdout} + harmlessOptions* = {optForceFullMake, optNoLinking, optRun, + optUseColors, optStdout} type TCommands* = enum # Nim's commands diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index a40f8ee66..bbcf25903 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -701,7 +701,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, # We need to put them in the current sigmatch's binding table in order for them # to be resolvable while matching the rest of the parameters for p in typeParams: - put(m.bindings, p[1], p[0].typ) + put(m, p[1], p[0].typ) if ff.kind == tyUserTypeClassInst: result = generateTypeInstance(c, m.bindings, Concept.sym.info, ff) @@ -831,7 +831,7 @@ proc inferStaticsInRange(c: var TCandidate, var rhs = r var inferred = inferStaticParam(exp, rhs) if inferred != nil: - put(c.bindings, inferred, inferred) + put(c, inferred, inferred) return isGeneric else: failureToInferStaticParam exp @@ -1612,8 +1612,8 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType, if a.kind == tyStatic: if m.callee.kind == tyGenericBody and - argType.n == nil and - tfGenericTypeParam notin argType.flags: + a.n == nil and + tfGenericTypeParam notin a.flags: return newNodeIT(nkType, argOrig.info, makeTypeFromExpr(c, arg)) else: var evaluated = c.semTryConstExpr(c, arg) diff --git a/tests/concepts/texplain.nim b/tests/concepts/texplain.nim index 186621f5b..25a075fd1 100644 --- a/tests/concepts/texplain.nim +++ b/tests/concepts/texplain.nim @@ -1,60 +1,60 @@ discard """ cmd: "nim c --verbosity:0 --colors:off $file" nimout: ''' -tests/concepts/texplain.nim(99, 10) Hint: Non-matching candidates for e(y) +texplain.nim(99, 10) Hint: Non-matching candidates for e(y) proc e(i: int): int -tests/concepts/texplain.nim(102, 7) Hint: Non-matching candidates for e(10) +texplain.nim(102, 7) Hint: Non-matching candidates for e(10) proc e(o: ExplainedConcept): int -tests/concepts/texplain.nim(65, 6) ExplainedConcept: undeclared field: 'foo' -tests/concepts/texplain.nim(65, 6) ExplainedConcept: undeclared field: '.' -tests/concepts/texplain.nim(65, 6) ExplainedConcept: expression '.' cannot be called -tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed -tests/concepts/texplain.nim(66, 6) ExplainedConcept: undeclared field: 'bar' -tests/concepts/texplain.nim(66, 6) ExplainedConcept: undeclared field: '.' -tests/concepts/texplain.nim(66, 6) ExplainedConcept: expression '.' cannot be called -tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed +texplain.nim(65, 6) ExplainedConcept: undeclared field: 'foo' +texplain.nim(65, 6) ExplainedConcept: undeclared field: '.' +texplain.nim(65, 6) ExplainedConcept: expression '.' cannot be called +texplain.nim(65, 5) ExplainedConcept: type class predicate failed +texplain.nim(66, 6) ExplainedConcept: undeclared field: 'bar' +texplain.nim(66, 6) ExplainedConcept: undeclared field: '.' +texplain.nim(66, 6) ExplainedConcept: expression '.' cannot be called +texplain.nim(65, 5) ExplainedConcept: type class predicate failed -tests/concepts/texplain.nim(105, 10) Hint: Non-matching candidates for e(10) +texplain.nim(105, 10) Hint: Non-matching candidates for e(10) proc e(o: ExplainedConcept): int -tests/concepts/texplain.nim(65, 6) ExplainedConcept: undeclared field: 'foo' -tests/concepts/texplain.nim(65, 6) ExplainedConcept: undeclared field: '.' -tests/concepts/texplain.nim(65, 6) ExplainedConcept: expression '.' cannot be called -tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed -tests/concepts/texplain.nim(66, 6) ExplainedConcept: undeclared field: 'bar' -tests/concepts/texplain.nim(66, 6) ExplainedConcept: undeclared field: '.' -tests/concepts/texplain.nim(66, 6) ExplainedConcept: expression '.' cannot be called -tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed +texplain.nim(65, 6) ExplainedConcept: undeclared field: 'foo' +texplain.nim(65, 6) ExplainedConcept: undeclared field: '.' +texplain.nim(65, 6) ExplainedConcept: expression '.' cannot be called +texplain.nim(65, 5) ExplainedConcept: type class predicate failed +texplain.nim(66, 6) ExplainedConcept: undeclared field: 'bar' +texplain.nim(66, 6) ExplainedConcept: undeclared field: '.' +texplain.nim(66, 6) ExplainedConcept: expression '.' cannot be called +texplain.nim(65, 5) ExplainedConcept: type class predicate failed -tests/concepts/texplain.nim(109, 20) Error: type mismatch: got (NonMatchingType) +texplain.nim(109, 20) Error: type mismatch: got (NonMatchingType) but expected one of: proc e(o: ExplainedConcept): int -tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed +texplain.nim(65, 5) ExplainedConcept: type class predicate failed proc e(i: int): int -tests/concepts/texplain.nim(110, 20) Error: type mismatch: got (NonMatchingType) +texplain.nim(110, 20) Error: type mismatch: got (NonMatchingType) but expected one of: proc r(o: RegularConcept): int -tests/concepts/texplain.nim(69, 5) RegularConcept: type class predicate failed +texplain.nim(69, 5) RegularConcept: type class predicate failed proc r[T](a: SomeNumber; b: T; c: auto) proc r(i: string): int -tests/concepts/texplain.nim(111, 20) Hint: Non-matching candidates for r(y) +texplain.nim(111, 20) Hint: Non-matching candidates for r(y) proc r[T](a: SomeNumber; b: T; c: auto) proc r(i: string): int -tests/concepts/texplain.nim(119, 2) Error: type mismatch: got (MatchingType) +texplain.nim(119, 2) Error: type mismatch: got (MatchingType) but expected one of: proc f(o: NestedConcept) -tests/concepts/texplain.nim(69, 6) RegularConcept: undeclared field: 'foo' -tests/concepts/texplain.nim(69, 6) RegularConcept: undeclared field: '.' -tests/concepts/texplain.nim(69, 6) RegularConcept: expression '.' cannot be called -tests/concepts/texplain.nim(69, 5) RegularConcept: type class predicate failed -tests/concepts/texplain.nim(70, 6) RegularConcept: undeclared field: 'bar' -tests/concepts/texplain.nim(70, 6) RegularConcept: undeclared field: '.' -tests/concepts/texplain.nim(70, 6) RegularConcept: expression '.' cannot be called -tests/concepts/texplain.nim(69, 5) RegularConcept: type class predicate failed -tests/concepts/texplain.nim(73, 5) NestedConcept: type class predicate failed +texplain.nim(69, 6) RegularConcept: undeclared field: 'foo' +texplain.nim(69, 6) RegularConcept: undeclared field: '.' +texplain.nim(69, 6) RegularConcept: expression '.' cannot be called +texplain.nim(69, 5) RegularConcept: type class predicate failed +texplain.nim(70, 6) RegularConcept: undeclared field: 'bar' +texplain.nim(70, 6) RegularConcept: undeclared field: '.' +texplain.nim(70, 6) RegularConcept: expression '.' cannot be called +texplain.nim(69, 5) RegularConcept: type class predicate failed +texplain.nim(73, 5) NestedConcept: type class predicate failed ''' line: 119 errormsg: "type mismatch: got (MatchingType)" From 9574d318ae1e4cf648f7384d99afadd8a2d89520 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 27 Jan 2017 01:21:35 +0200 Subject: [PATCH 21/37] use lower-case proc names --- compiler/semmagic.nim | 8 ++++---- doc/manual/generics.txt | 2 +- lib/pure/typetraits.nim | 10 +++++----- tests/concepts/matrixalgo.nim | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index ee6329125..3e1989eaf 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -91,7 +91,7 @@ proc toNode(t: PType, i: TLineInfo): PNode = const # these are types that use the bracket syntax for instantiation - # they can be subjected to the type traits `GenericHead` and + # they can be subjected to the type traits `genericHead` and # `Uninstantiated` tyUserDefinedGenerics* = {tyGenericInst, tyGenericInvocation, tyUserTypeClassInst} @@ -120,15 +120,15 @@ proc evalTypeTrait(trait: PNode, operand: PType, context: PSym): PNode = result = newIntNode(nkIntLit, typ.len - ord(typ.kind==tyProc)) result.typ = newType(tyInt, context) result.info = trait.info - of "GenericHead": + of "genericHead": var res = uninstantiate(typ) if res == typ and res.kind notin tyMagicGenerics: localError(trait.info, - "GenericHead expects a generic type. The given type was " & + "genericHead expects a generic type. The given type was " & typeToString(typ)) return newType(tyError, context).toNode(trait.info) result = res.base.toNode(trait.info) - of "StripGenericParams": + of "stripGenericParams": result = uninstantiate(typ).toNode(trait.info) else: internalAssert false diff --git a/doc/manual/generics.txt b/doc/manual/generics.txt index d1af6d962..d0a60b62a 100644 --- a/doc/manual/generics.txt +++ b/doc/manual/generics.txt @@ -317,7 +317,7 @@ The concept types can be parametric just like the regular generic types: m[int, int] is T mvar[int, int] = T - type TransposedType = StripGenericParams(M)[C, R, T] + type TransposedType = stripGenericParams(M)[C, R, T] AnySquareMatrix*[N: static[int], T] = AnyMatrix[N, N, T] diff --git a/lib/pure/typetraits.nim b/lib/pure/typetraits.nim index cc7fb9f72..55c4bf038 100644 --- a/lib/pure/typetraits.nim +++ b/lib/pure/typetraits.nim @@ -34,18 +34,18 @@ proc name*(t: typedesc): string {.magic: "TypeTrait".} proc arity*(t: typedesc): int {.magic: "TypeTrait".} ## Returns the arity of the given type -proc GenericHead*(t: typedesc): typedesc {.magic: "TypeTrait".} +proc genericHead*(t: typedesc): typedesc {.magic: "TypeTrait".} ## Accepts an instantiated generic type and returns its ## uninstantiated form. ## ## For example: - ## seq[int].GenericHead will be just seq - ## seq[int].GenericHead[float] will be seq[float] + ## seq[int].genericHead will be just seq + ## seq[int].genericHead[float] will be seq[float] ## ## A compile-time error will be produced if the supplied type ## is not generic -proc StripGenericParams*(t: typedesc): typedesc {.magic: "TypeTrait".} - ## This trait is similar to `GenericHead`, but instead of producing +proc stripGenericParams*(t: typedesc): typedesc {.magic: "TypeTrait".} + ## This trait is similar to `genericHead`, but instead of producing ## error for non-generic types, it will just return them unmodified diff --git a/tests/concepts/matrixalgo.nim b/tests/concepts/matrixalgo.nim index d02c70d37..39cf16685 100644 --- a/tests/concepts/matrixalgo.nim +++ b/tests/concepts/matrixalgo.nim @@ -9,7 +9,7 @@ type m[int, int] is T mvar[int, int] = T - type TransposedType = StripGenericParams(M)[C, R, T] + type TransposedType = stripGenericParams(M)[C, R, T] AnySquareMatrix*[N: static[int], T] = AnyMatrix[N, N, T] From 7fdf29937d8cb4835953a5e52b59e5f550d0fba2 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 24 Mar 2017 14:18:50 +0200 Subject: [PATCH 22/37] fix tgenericshardcases --- compiler/seminst.nim | 3 ++- compiler/semtypes.nim | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/compiler/seminst.nim b/compiler/seminst.nim index 71752f5c3..874be8dd6 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -304,7 +304,8 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, pragma(c, result, n.sons[pragmasPos], allRoutinePragmas) if isNil(n.sons[bodyPos]): n.sons[bodyPos] = copyTree(fn.getBody) - instantiateBody(c, n, fn.typ.n, result, fn) + if c.inGenericContext == 0: + instantiateBody(c, n, fn.typ.n, result, fn) sideEffectsCheck(c, result) paramsTypeCheck(c, result.typ) else: diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 42ffaa5fa..02166f0aa 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -185,12 +185,16 @@ proc semRangeAux(c: PContext, n: PNode, prev: PType): PType = for i in 0..1: rangeT[i] = range[i].typ.skipTypes({tyStatic}).skipIntLit - if not sameType(rangeT[0].skipTypes({tyRange}), rangeT[1].skipTypes({tyRange})): - localError(n.info, errPureTypeMismatch) - elif not rangeT[0].isOrdinalType: - localError(n.info, errOrdinalTypeExpected) - elif enumHasHoles(rangeT[0]): - localError(n.info, errEnumXHasHoles, rangeT[0].sym.name.s) + let hasUnknownTypes = c.inGenericContext > 0 and + rangeT[0].kind == tyFromExpr or rangeT[1].kind == tyFromExpr + + if not hasUnknownTypes: + if not sameType(rangeT[0].skipTypes({tyRange}), rangeT[1].skipTypes({tyRange})): + localError(n.info, errPureTypeMismatch) + elif not rangeT[0].isOrdinalType: + localError(n.info, errOrdinalTypeExpected) + elif enumHasHoles(rangeT[0]): + localError(n.info, errEnumXHasHoles, rangeT[0].sym.name.s) for i in 0..1: if hasGenericArguments(range[i]): @@ -228,7 +232,8 @@ proc semRange(c: PContext, n: PNode, prev: PType): PType = result = newOrPrevType(tyError, prev, c) proc semArrayIndex(c: PContext, n: PNode): PType = - if isRange(n): result = semRangeAux(c, n, nil) + if isRange(n): + result = semRangeAux(c, n, nil) else: let e = semExprWithType(c, n, {efDetermineType}) if e.typ.kind == tyFromExpr: From e3500ba4581a1a75bba474eb5dbf68676399514a Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 24 Mar 2017 14:23:37 +0200 Subject: [PATCH 23/37] Make tgc_unsafe2 green again apparently, the error reporting code introduced for concepts results in a slightly diffent output for this test case. --- tests/parallel/tgc_unsafe2.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/parallel/tgc_unsafe2.nim b/tests/parallel/tgc_unsafe2.nim index 4a5f36f6d..40bfbdadb 100644 --- a/tests/parallel/tgc_unsafe2.nim +++ b/tests/parallel/tgc_unsafe2.nim @@ -1,8 +1,8 @@ discard """ line: 28 - nimout: '''tgc_unsafe2.nim(22, 5) Warning: 'trick' is not GC-safe as it accesses 'global' which is a global using GC'ed memory -tgc_unsafe2.nim(26, 5) Warning: 'track' is not GC-safe as it calls 'trick' -tgc_unsafe2.nim(28, 5) Error: 'consumer' is not GC-safe as it calls 'track' + nimout: '''tgc_unsafe2.nim(22, 6) Warning: 'trick' is not GC-safe as it accesses 'global' which is a global using GC'ed memory [GcUnsafe2] +tgc_unsafe2.nim(26, 6) Warning: 'track' is not GC-safe as it calls 'trick' [GcUnsafe2] +tgc_unsafe2.nim(28, 6) Error: 'consumer' is not GC-safe as it calls 'track' ''' errormsg: "'consumer' is not GC-safe as it calls 'track'" """ From 79881bfce0970098b6dc38f6fbb18a86b54c7fad Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 24 Mar 2017 14:33:53 +0200 Subject: [PATCH 24/37] close #976 --- tests/concepts/t976.nim | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/concepts/t976.nim diff --git a/tests/concepts/t976.nim b/tests/concepts/t976.nim new file mode 100644 index 000000000..cc0bbdc59 --- /dev/null +++ b/tests/concepts/t976.nim @@ -0,0 +1,32 @@ +import macros + +type + int1 = distinct int + int2 = distinct int + + int1g = concept x + x is int1 + + int2g = concept x + x is int2 + +proc take[T: int1g](value: int1) = + when T is int2: + static: error("killed in take(int1)") + +proc take[T: int2g](vale: int2) = + when T is int1: + static: error("killed in take(int2)") + +var i1: int1 = 1.int1 +var i2: int2 = 2.int2 + +take[int1](i1) +take[int2](i2) + +template reject(e) = + static: assert(not compiles(e)) + +reject take[string](i2) +reject take[int1](i2) + From 905a22d137288bea8017dff42381b64fbe6387cb Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 24 Mar 2017 15:21:29 +0200 Subject: [PATCH 25/37] close #1128 --- tests/concepts/t1128.nim | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/concepts/t1128.nim diff --git a/tests/concepts/t1128.nim b/tests/concepts/t1128.nim new file mode 100644 index 000000000..7f7525a13 --- /dev/null +++ b/tests/concepts/t1128.nim @@ -0,0 +1,21 @@ +discard """ + output: "true\ntrue" +""" + +type + TFooContainer[T] = object + + TContainer[T] = generic var c + foo(c, T) + +proc foo[T](c: var TFooContainer[T], val: T) = + discard + +proc bar(c: var TContainer) = + discard + +var fooContainer: TFooContainer[int] +echo fooContainer is TFooContainer # true. +echo fooContainer is TFooContainer[int] # true. +fooContainer.bar() + From e31575c27a7ef9543257aa613515fff7adf4ac84 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 24 Mar 2017 15:25:11 +0200 Subject: [PATCH 26/37] close #1147 --- tests/concepts/tmisc_issues.nim | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/concepts/tmisc_issues.nim diff --git a/tests/concepts/tmisc_issues.nim b/tests/concepts/tmisc_issues.nim new file mode 100644 index 000000000..fad1cd6a7 --- /dev/null +++ b/tests/concepts/tmisc_issues.nim @@ -0,0 +1,16 @@ +discard """ +output: '''true''' +""" + +# https://github.com/nim-lang/Nim/issues/1147 +type TTest = object + vals: seq[int] + +proc add*(self: var TTest, val: int) = + self.vals.add(val) + +type CAddable = concept x + x[].add(int) + +echo((ref TTest) is CAddable) + From 52377740b6fe85b03f02552117ac92b82c2809a8 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 24 Mar 2017 15:29:40 +0200 Subject: [PATCH 27/37] close #1570 --- tests/concepts/tmisc_issues.nim | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/concepts/tmisc_issues.nim b/tests/concepts/tmisc_issues.nim index fad1cd6a7..556e0b9f5 100644 --- a/tests/concepts/tmisc_issues.nim +++ b/tests/concepts/tmisc_issues.nim @@ -1,5 +1,8 @@ discard """ -output: '''true''' +output: '''true +true +true +true''' """ # https://github.com/nim-lang/Nim/issues/1147 @@ -14,3 +17,20 @@ type CAddable = concept x echo((ref TTest) is CAddable) +# https://github.com/nim-lang/Nim/issues/1570 +type ConcretePointOfFloat = object + x, y: float + +type ConcretePoint[Value] = object + x, y: Value + +type AbstractPointOfFloat = generic p + p.x is float and p.y is float + +let p1 = ConcretePointOfFloat(x: 0, y: 0) +let p2 = ConcretePoint[float](x: 0, y: 0) + +echo p1 is AbstractPointOfFloat # true +echo p2 is AbstractPointOfFloat # true +echo p2.x is float and p2.y is float # true + From f8c921dd25f71cb7ac5975d743ee36cdb424954b Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 24 Mar 2017 15:47:34 +0200 Subject: [PATCH 28/37] close #1991 --- .../tconcepts_overload_precedence.nim | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/concepts/tconcepts_overload_precedence.nim diff --git a/tests/concepts/tconcepts_overload_precedence.nim b/tests/concepts/tconcepts_overload_precedence.nim new file mode 100644 index 000000000..4180b6d64 --- /dev/null +++ b/tests/concepts/tconcepts_overload_precedence.nim @@ -0,0 +1,69 @@ +discard """ + ouput: '''x as ParameterizedType[T] +x as ParameterizedType[T] +x as ParameterizedType[T] +x as ParameterizedType +x as ParameterizedType +x as CustomTypeClass''' +""" + +type ParameterizedType[T] = object + +type CustomTypeClass = concept + true + +# 3 competing procs +proc a[T](x: ParameterizedType[T]) = + echo "x as ParameterizedType[T]" + +proc a(x: ParameterizedType) = + echo "x as ParameterizedType" + +proc a(x: CustomTypeClass) = + echo "x as CustomTypeClass" + +# the same procs in different order +proc b(x: ParameterizedType) = + echo "x as ParameterizedType" + +proc b(x: CustomTypeClass) = + echo "x as CustomTypeClass" + +proc b[T](x: ParameterizedType[T]) = + echo "x as ParameterizedType[T]" + +# and yet another order +proc c(x: CustomTypeClass) = + echo "x as CustomTypeClass" + +proc c(x: ParameterizedType) = + echo "x as ParameterizedType" + +proc c[T](x: ParameterizedType[T]) = + echo "x as ParameterizedType[T]" + +# remove the most specific one +proc d(x: ParameterizedType) = + echo "x as ParameterizedType" + +proc d(x: CustomTypeClass) = + echo "x as CustomTypeClass" + +# then shuffle the order again +proc e(x: CustomTypeClass) = + echo "x as CustomTypeClass" + +proc e(x: ParameterizedType) = + echo "x as ParameterizedType" + +# the least specific one is a match +proc f(x: CustomTypeClass) = + echo "x as CustomTypeClass" + +a(ParameterizedType[int]()) +b(ParameterizedType[int]()) +c(ParameterizedType[int]()) +d(ParameterizedType[int]()) +e(ParameterizedType[int]()) +f(ParameterizedType[int]()) + From d16557bbdb27a9022c39bdfc2d42295cc954724d Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 24 Mar 2017 15:53:56 +0200 Subject: [PATCH 29/37] close #2018 --- tests/concepts/tmisc_issues.nim | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/concepts/tmisc_issues.nim b/tests/concepts/tmisc_issues.nim index 556e0b9f5..5fc4e2116 100644 --- a/tests/concepts/tmisc_issues.nim +++ b/tests/concepts/tmisc_issues.nim @@ -2,7 +2,9 @@ discard """ output: '''true true true -true''' +true +p has been called. +p has been called.''' """ # https://github.com/nim-lang/Nim/issues/1147 @@ -15,7 +17,7 @@ proc add*(self: var TTest, val: int) = type CAddable = concept x x[].add(int) -echo((ref TTest) is CAddable) +echo((ref TTest) is CAddable) # true # https://github.com/nim-lang/Nim/issues/1570 type ConcretePointOfFloat = object @@ -34,3 +36,16 @@ echo p1 is AbstractPointOfFloat # true echo p2 is AbstractPointOfFloat # true echo p2.x is float and p2.y is float # true +# https://github.com/nim-lang/Nim/issues/2018 +type ProtocolFollower = generic + true # not a particularly involved protocol + +type ImplementorA = object +type ImplementorB = object + +proc p[A: ProtocolFollower, B: ProtocolFollower](a: A, b: B) = + echo "p has been called." + +p(ImplementorA(), ImplementorA()) +p(ImplementorA(), ImplementorB()) + From 189d28672cd72053040cafe632c87b1d5cbbc364 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 24 Mar 2017 16:02:10 +0200 Subject: [PATCH 30/37] close #2423 --- tests/concepts/tmisc_issues.nim | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/concepts/tmisc_issues.nim b/tests/concepts/tmisc_issues.nim index 5fc4e2116..233739580 100644 --- a/tests/concepts/tmisc_issues.nim +++ b/tests/concepts/tmisc_issues.nim @@ -4,7 +4,9 @@ true true true p has been called. -p has been called.''' +p has been called. +implicit generic +generic''' """ # https://github.com/nim-lang/Nim/issues/1147 @@ -49,3 +51,18 @@ proc p[A: ProtocolFollower, B: ProtocolFollower](a: A, b: B) = p(ImplementorA(), ImplementorA()) p(ImplementorA(), ImplementorB()) +# https://github.com/nim-lang/Nim/issues/2423 +proc put*[T](c: seq[T], x: T) = echo "generic" +proc put*(c: seq) = echo "implicit generic" + +type + Container[T] = concept c + put(c) + put(c, T) + +proc c1(x: Container) = echo "implicit generic" +c1(@[1]) + +proc c2[T](x: Container[T]) = echo "generic" +c2(@[1]) + From bec7f9398fe3b90089d4ffb8f0da5ec8574a891d Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 24 Mar 2017 16:07:17 +0200 Subject: [PATCH 31/37] close #2882 --- tests/concepts/tmisc_issues.nim | 34 ++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/tests/concepts/tmisc_issues.nim b/tests/concepts/tmisc_issues.nim index 233739580..10e072521 100644 --- a/tests/concepts/tmisc_issues.nim +++ b/tests/concepts/tmisc_issues.nim @@ -6,7 +6,10 @@ true p has been called. p has been called. implicit generic -generic''' +generic +false +true +-1''' """ # https://github.com/nim-lang/Nim/issues/1147 @@ -66,3 +69,32 @@ c1(@[1]) proc c2[T](x: Container[T]) = echo "generic" c2(@[1]) +# https://github.com/nim-lang/Nim/issues/2882 +type + Paper = object + name: string + + Bendable = concept x + bend(x is Bendable) + +proc bend(p: Paper): Paper = Paper(name: "bent-" & p.name) + +var paper = Paper(name: "red") +echo paper is Bendable + +type + A = concept self + size(self) is int + + B = object + +proc size(self: B): int = + return -1 + +proc size(self: A): int = + return 0 + +let b = B() +echo b is A +echo b.size() + From 78ca4f6be4980b6f37b705ce4fd390976f044c4e Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 24 Mar 2017 16:21:14 +0200 Subject: [PATCH 32/37] close #3330 --- tests/concepts/t3330.nim | 41 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 tests/concepts/t3330.nim diff --git a/tests/concepts/t3330.nim b/tests/concepts/t3330.nim new file mode 100644 index 000000000..04add2b6f --- /dev/null +++ b/tests/concepts/t3330.nim @@ -0,0 +1,41 @@ +discard """ +errormsg: "type mismatch: got (Bar[system.int])" +nimout: ''' +t3330.nim(40, 4) Error: type mismatch: got (Bar[system.int]) +but expected one of: +proc test(foo: Foo[int]) +t3330.nim(25, 8) Hint: Non-matching candidates for add(k, string, T) +proc add[T](x: var seq[T]; y: T) +proc add(result: var string; x: float) +proc add(x: var string; y: string) +proc add(x: var string; y: cstring) +proc add(x: var string; y: char) +proc add(result: var string; x: int64) +proc add[T](x: var seq[T]; y: openArray[T]) + +t3330.nim(25, 8) template/generic instantiation from here +t3330.nim(32, 6) Foo: 'bar.value' cannot be assigned to +t3330.nim(25, 8) template/generic instantiation from here +t3330.nim(33, 6) Foo: 'bar.x' cannot be assigned to +''' +""" + +type + Foo[T] = concept k + add(k, string, T) + + Bar[T] = object + value: T + x: string + +proc add[T](bar: Bar[T], x: string, val: T) = + bar.value = val + bar.x = x + +proc test(foo: Foo[int]) = + foo.add("test", 42) + echo(foo.x) + +var bar = Bar[int]() +bar.test() + From b32576a59cd49081646b7873346715c64e1ad3ed Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 24 Mar 2017 17:31:12 +0200 Subject: [PATCH 33/37] fix a typo --- tests/concepts/tconcepts_overload_precedence.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/concepts/tconcepts_overload_precedence.nim b/tests/concepts/tconcepts_overload_precedence.nim index 4180b6d64..9eed6256a 100644 --- a/tests/concepts/tconcepts_overload_precedence.nim +++ b/tests/concepts/tconcepts_overload_precedence.nim @@ -1,5 +1,5 @@ discard """ - ouput: '''x as ParameterizedType[T] + output: '''x as ParameterizedType[T] x as ParameterizedType[T] x as ParameterizedType[T] x as ParameterizedType From 01bc5f32d665ea1c72590609391769cef60d6e83 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Tue, 28 Mar 2017 01:58:54 +0300 Subject: [PATCH 34/37] Fix the compilation of the emmy package --- compiler/semstmts.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index d42eb5433..12391a9cd 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -500,6 +500,8 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = #changeType(def.skipConv, typ, check=true) else: typ = skipIntLit(def.typ) + if typ.kind in tyUserTypeClasses and typ.isResolvedUserTypeClass: + typ = typ.lastSon if hasEmpty(typ): localError(def.info, errCannotInferTypeOfTheLiteral, ($typ.kind).substr(2).toLowerAscii) From 01207b6cfda148c513d5b3fe7db024d1b5881a95 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Tue, 28 Mar 2017 14:19:32 +0300 Subject: [PATCH 35/37] restore the T1-T9 types and clarify how generic concepts work --- doc/manual/generics.txt | 31 +++++++++++++++++++++++++------ lib/system.nim | 11 +++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/doc/manual/generics.txt b/doc/manual/generics.txt index d0a60b62a..bb5d0ab2c 100644 --- a/doc/manual/generics.txt +++ b/doc/manual/generics.txt @@ -116,7 +116,8 @@ type class matches ``array`` any array type ``set`` any set type ``seq`` any seq type -``any`` any type +``auto`` any type +``any`` distinct auto (see below) ================== =================================================== Furthermore, every generic type automatically creates a type class of the same @@ -196,6 +197,9 @@ supply all type parameters of the generic type, because any missing ones will be inferred to have the equivalent of the `any` type class and thus they will match anything without discrimination. +To help you write more concise implicitly generic procs, the Nim's system +module includes the named types `T1` through `T9` which are bind once aliases +of the `auto` type. Concepts -------- @@ -273,7 +277,7 @@ value that will be matched only as a type. Please note that the ``is`` operator allows one to easily verify the precise type signatures of the required operations, but since type inference and default parameters are still applied in the concept body, it's also possible -to encode usage protocols that do not reveal implementation details. +to describe usage protocols that do not reveal implementation details. Much like generics, concepts are instantiated exactly once for each tested type and any static code included within the body is executed only once. @@ -378,8 +382,8 @@ operator and also when types dependent on them are being matched: .. code-block:: nim type - MyConcept[M, N: static[int]; T] = concept x - x.foo(SquareMatrix[N, T]) is array[M, int] + MatrixReducer[M, N: static[int]; T] = concept x + x.reduce(SquareMatrix[N, T]) is array[M, int] The Nim compiler includes a simple linear equation solver, allowing it to infer static params in some situations where integer arithmetic is involved. @@ -410,7 +414,7 @@ to match several procs accepting the same wide class of types: On the other hand, using ``bind once`` types allows you to test for equivalent types used in multiple signatures, without actually requiring any concrete -types, thus allowing you to encode implementation detail types: +types, thus allowing you to encode implementation-defined types: .. code-block:: nim type @@ -425,10 +429,25 @@ types, thus allowing you to encode implementation detail types: # and it must be a numeric sequence As seen in the previous examples, you can refer to generic concepts such as -Enumerable[T] just by their short name. Much like the regular generic types, +`Enumerable[T]` just by their short name. Much like the regular generic types, the concept will be automatically instantiated with the bind once auto type in the place of each missing generic param. +Please note that generic concepts such as `Enumerable[T]` can be matched +against concrete types such as `string`. Nim doesn't require the concept +type to have the same number of parameters as the type being matched. +In order to express such a requirement, you'll need to rely on a type +mapping operator such a `genericHead` or `stripGenericParams` within the +concept body: + +.. code-block:: nim + import future, typetraits + + type + Functor[A] = concept f + f.value is A + map(f, A -> T1) is genericHead(f.type)[T1] + Concept derived values ---------------------- diff --git a/lib/system.nim b/lib/system.nim index 94e10d7df..0e777b707 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -98,6 +98,17 @@ type SomeNumber* = SomeInteger|SomeReal ## type class matching all number types + T1* = auto + T2* = auto + T3* = auto + T4* = auto + T5* = auto + T6* = auto + T7* = auto + T8* = auto + T9* = auto + ## Helper types for writing implicitly generic procs + proc defined*(x: untyped): bool {.magic: "Defined", noSideEffect, compileTime.} ## Special compile-time procedure that checks whether `x` is ## defined. From a74ad869e905737f144037f4ad5000eaed7a5bd2 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Wed, 29 Mar 2017 16:15:30 +0300 Subject: [PATCH 36/37] requested code review changes --- compiler/semcall.nim | 30 ++++++++++++++++++------------ compiler/semstmts.nim | 17 ++++++++++++----- compiler/semtypes.nim | 2 +- compiler/sigmatch.nim | 24 ++++++++++++------------ doc/manual/generics.txt | 31 +++++++++++++++++++++++-------- lib/system.nim | 11 ----------- 6 files changed, 66 insertions(+), 49 deletions(-) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index e1f09c3d5..f707fc844 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -99,7 +99,10 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, if cmp < 0: best = z # x is better than the best so far elif cmp == 0: alt = z # x is as good as the best so far elif errors != nil or z.diagnostics != nil: - errors.safeAdd((sym, int z.mutabilityProblem, z.diagnostics)) + errors.safeAdd(CandidateError( + sym: sym, + unmatchedVarParam: int z.mutabilityProblem, + diagnostics: z.diagnostics)) else: # Symbol table has been modified. Restart and pre-calculate all syms # before any further candidate init and compare. SLOW, but rare case. @@ -126,9 +129,9 @@ proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors): # we do a pre-analysis. If all types produce the same string, we will add # module information. let proto = describeArgs(c, n, 1, preferName) - for err, mut, diagnostics in items(errors): + for err in errors: var errProto = "" - let n = err.typ.n + let n = err.sym.typ.n for i in countup(1, n.len - 1): var p = n.sons[i] if p.kind == nkSym: @@ -140,16 +143,17 @@ proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors): break var candidates = "" - for err, mut, diagnostics in items(errors): - if err.kind in routineKinds and err.ast != nil: - add(candidates, renderTree(err.ast, + for err in errors: + if err.sym.kind in routineKinds and err.sym.ast != nil: + add(candidates, renderTree(err.sym.ast, {renderNoBody, renderNoComments, renderNoPragmas})) else: - add(candidates, err.getProcHeader(prefer)) + add(candidates, err.sym.getProcHeader(prefer)) add(candidates, "\n") - if mut != 0 and mut < n.len: - add(candidates, "for a 'var' type a variable needs to be passed, but '" & renderTree(n[mut]) & "' is immutable\n") - for diag in diagnostics: + if err.unmatchedVarParam != 0 and err.unmatchedVarParam < n.len: + add(candidates, "for a 'var' type a variable needs to be passed, but '" & + renderTree(n[err.unmatchedVarParam]) & "' is immutable\n") + for diag in err.diagnostics: add(candidates, diag & "\n") result = (prefer, candidates) @@ -180,7 +184,9 @@ proc bracketNotFoundError(c: PContext; n: PNode) = var symx = initOverloadIter(o, c, headSymbol) while symx != nil: if symx.kind in routineKinds: - errors.add((symx, 0, nil)) + errors.add(CandidateError(sym: symx, + unmatchedVarParam: 0, + diagnostics: nil)) symx = nextOverloadIter(o, c, headSymbol) if errors.len == 0: localError(n.info, "could not resolve: " & $n) @@ -405,7 +411,7 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode, n.sons[1] = n.sons[1].sons[0] notFoundError(c, n, errors) else: - if efExplain notin flags: + if efExplain notin flags and c.compilesContextId == 0: # repeat the overload resolution, # this time enabling all the diagnostic output (this should fail again) discard semOverloadedCall(c, n, nOrig, filter, flags + {efExplain}) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 12391a9cd..a678311bf 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1555,9 +1555,14 @@ proc usesResult(n: PNode): bool = for c in n: if usesResult(c): return true -proc inferConceptStaticParam(c: PContext, typ: PType, n: PNode) = +proc inferConceptStaticParam(c: PContext, inferred, n: PNode) = + var typ = inferred.typ let res = semConstExpr(c, n) - if not sameType(res.typ, typ.base): localError(n.info, "") + if not sameType(res.typ, typ.base): + localError(n.info, + "cannot infer the concept parameter '%s', due to a type mismatch. " & + "attempt to equate '%s' and '%s'.", + [inferred.renderTree, $res.typ, $typ.base]) typ.n = res proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = @@ -1616,12 +1621,14 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = if c.inTypeClass > 0 and expr.typ != nil: case expr.typ.kind of tyBool: - if expr.kind == nkInfix and expr[0].sym.name.s == "==": + if expr.kind == nkInfix and + expr[0].kind == nkSym and + expr[0].sym.name.s == "==": if expr[1].typ.isUnresolvedStatic: - inferConceptStaticParam(c, expr[1].typ, expr[2]) + inferConceptStaticParam(c, expr[1], expr[2]) continue elif expr[2].typ.isUnresolvedStatic: - inferConceptStaticParam(c, expr[2].typ, expr[1]) + inferConceptStaticParam(c, expr[2], expr[1]) continue let verdict = semConstExpr(c, n[i]) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 02166f0aa..422d2f0fa 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1235,7 +1235,7 @@ proc symFromExpectedTypeNode(c: PContext, n: PNode): PSym = if n.kind == nkType: result = symFromType(n.typ, n.info) else: - localError(n.info, "xx") + localError(n.info, errTypeExpected) result = errorSym(c, n) proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index bbcf25903..ff7b0ae72 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -22,10 +22,10 @@ type TCandidateState* = enum csEmpty, csMatch, csNoMatch - CandidateError = tuple - sym: PSym - unmatchedVarParam: int - diagnostics: seq[string] + CandidateError* = object + sym*: PSym + unmatchedVarParam*: int + diagnostics*: seq[string] CandidateErrors* = seq[CandidateError] @@ -589,8 +589,8 @@ proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} = proc matchUserTypeClass*(c: PContext, m: var TCandidate, ff, a: PType): PType = var - Concept = ff.skipTypes({tyUserTypeClassInst}) - body = Concept.n[3] + typeClass = ff.skipTypes({tyUserTypeClassInst}) + body = typeClass.n[3] if c.inTypeClass > 4: localError(body.info, $body & " too nested for type matching") return nil @@ -611,7 +611,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, param: PSym template paramSym(kind): untyped = - newSym(kind, typeParamName, Concept.sym, Concept.sym.info) + newSym(kind, typeParamName, typeClass.sym, typeClass.sym.info) block addTypeParam: for prev in typeParams: @@ -642,7 +642,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, addDecl(c, param) - for param in Concept.n[0]: + for param in typeClass.n[0]: var dummyName: PNode dummyType: PType @@ -665,7 +665,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, internalAssert dummyName.kind == nkIdent var dummyParam = newSym(if modifier == tyTypeDesc: skType else: skVar, - dummyName.ident, Concept.sym, Concept.sym.info) + dummyName.ident, typeClass.sym, typeClass.sym.info) dummyParam.typ = dummyType addDecl(c, dummyParam) @@ -675,7 +675,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, errorPrefix: string flags: TExprFlags = {} collectDiagnostics = m.diagnostics != nil or - sfExplain in Concept.sym.flags + sfExplain in typeClass.sym.flags if collectDiagnostics: oldWriteHook = writelnHook @@ -684,7 +684,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, diagnostics = @[] flags = {efExplain} writelnHook = proc (s: string) = - if errorPrefix == nil: errorPrefix = Concept.sym.name.s & ":" + if errorPrefix == nil: errorPrefix = typeClass.sym.name.s & ":" let msg = s.replace("Error:", errorPrefix) if oldWriteHook != nil: oldWriteHook msg diagnostics.add msg @@ -704,7 +704,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, put(m, p[1], p[0].typ) if ff.kind == tyUserTypeClassInst: - result = generateTypeInstance(c, m.bindings, Concept.sym.info, ff) + result = generateTypeInstance(c, m.bindings, typeClass.sym.info, ff) else: result = copyType(ff, ff.owner, true) diff --git a/doc/manual/generics.txt b/doc/manual/generics.txt index bb5d0ab2c..87fcb7828 100644 --- a/doc/manual/generics.txt +++ b/doc/manual/generics.txt @@ -197,9 +197,6 @@ supply all type parameters of the generic type, because any missing ones will be inferred to have the equivalent of the `any` type class and thus they will match anything without discrimination. -To help you write more concise implicitly generic procs, the Nim's system -module includes the named types `T1` through `T9` which are bind once aliases -of the `auto` type. Concepts -------- @@ -436,17 +433,35 @@ in the place of each missing generic param. Please note that generic concepts such as `Enumerable[T]` can be matched against concrete types such as `string`. Nim doesn't require the concept type to have the same number of parameters as the type being matched. -In order to express such a requirement, you'll need to rely on a type -mapping operator such a `genericHead` or `stripGenericParams` within the -concept body: +If you wish to express a requirement towards the generic parameters of +the matched type, you can use a type mapping operator such as `genericHead` +or `stripGenericParams` within the body of the concept to obtain the +uninstantiated version of the type, which you can then try to instantiate +in any required way. For example, here is how one might define the classic +`Functor` concept from Haskell and then demonstrate that Nim's `Option[T]` +type is an instance of it: .. code-block:: nim import future, typetraits type Functor[A] = concept f - f.value is A - map(f, A -> T1) is genericHead(f.type)[T1] + type MatchedGenericType = genericHead(f.type) + # `f` will be a value of a type such as `Option[T]` + # `MatchedGenericType` will become the `Option` type + + f.val is A + # The Functor should provide a way to obtain + # a value stored inside it + + type T = auto + map(f, A -> T) is MatchedGenericType[T] + # And it should provide a way to map one instance of + # the Functor to a instance of a different type, given + # a suitable `map` operation for the enclosed values + + import options + echo Option[int] is Functor # prints true Concept derived values diff --git a/lib/system.nim b/lib/system.nim index 0e777b707..94e10d7df 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -98,17 +98,6 @@ type SomeNumber* = SomeInteger|SomeReal ## type class matching all number types - T1* = auto - T2* = auto - T3* = auto - T4* = auto - T5* = auto - T6* = auto - T7* = auto - T8* = auto - T9* = auto - ## Helper types for writing implicitly generic procs - proc defined*(x: untyped): bool {.magic: "Defined", noSideEffect, compileTime.} ## Special compile-time procedure that checks whether `x` is ## defined. From c11d7c35dd0dbe54cef108988955c48f4e1fab48 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 31 Mar 2017 01:53:11 +0300 Subject: [PATCH 37/37] make all tests green again properly disabling the extra diagnostics in semCompiles proved to be too much work. I have some plans for further improving the error messages in the future and I'll revisit this later. --- compiler/semcall.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index f707fc844..d6852859b 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -411,7 +411,7 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode, n.sons[1] = n.sons[1].sons[0] notFoundError(c, n, errors) else: - if efExplain notin flags and c.compilesContextId == 0: + if efExplain notin flags: # repeat the overload resolution, # this time enabling all the diagnostic output (this should fail again) discard semOverloadedCall(c, n, nOrig, filter, flags + {efExplain})