diff --git a/compiler/ast.nim b/compiler/ast.nim index 7104c020bc..edaf7d88aa 100755 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -310,6 +310,8 @@ type tfFromGeneric # type is an instantiation of a generic; this is needed # because for instantiations of objects, structural # type equality has to be used + tfAll # type class requires all constraints to be met (default) + tfAny # type class requires any constraint to be met TTypeFlags* = set[TTypeFlag] diff --git a/compiler/msgs.nim b/compiler/msgs.nim index ef4a0a4cfd..84978ecf99 100755 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -662,3 +662,8 @@ proc InternalError*(info: TLineInfo, errMsg: string) = proc InternalError*(errMsg: string) = writeContext(UnknownLineInfo()) rawMessage(errInternal, errMsg) + +template AssertNotNull*(e: expr): expr = + if(e == nil): InternalError($InstantiationInfo()) + e + diff --git a/compiler/options.nim b/compiler/options.nim index ff51ad66c2..edea2288d5 100755 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -219,3 +219,6 @@ proc binaryStrSearch*(x: openarray[string], y: string): int = return mid result = - 1 +# Can we keep this? I'm using it all the time +template nimdbg*: expr = c.filename.endsWith"nimdbg.nim" + diff --git a/compiler/seminst.nim b/compiler/seminst.nim index 9ec78488b2..bc8c1d9643 100755 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -20,7 +20,7 @@ proc instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable, if a.kind != nkSym: InternalError(a.info, "instantiateGenericParamList; no symbol") var q = a.sym - if q.typ.kind notin {tyTypeDesc, tyGenericParam}: continue + if q.typ.kind notin {tyTypeDesc, tyGenericParam, tyTypeClass}: continue var s = newSym(skType, q.name, getCurrOwner()) s.info = q.info s.flags = s.flags + {sfUsed, sfFromGeneric} @@ -107,8 +107,6 @@ proc sideEffectsCheck(c: PContext, s: PSym) = s.ast.sons[genericParamsPos].kind == nkEmpty: c.threadEntries.add(s) -template nimdbg: expr = c.filename.endsWith"nimdbg.nim" - proc applyConcreteTypesToSig(genericProc: PSym, concTypes: seq[PType]): PType = # XXX: This is intended to replace the use of semParamList in generateInstance. # The results of semParamList's analysis are already encoded in the original diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 0ae4ae5fd0..4c6bc36a38 100755 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -475,42 +475,6 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType): PType = result.n = newNodeI(nkRecList, n.info) semRecordNodeAux(c, n.sons[2], check, pos, result.n, result.sym) -proc addTypeVarsOfGenericBody(c: PContext, t: PType, genericParams: PNode, - cl: var TIntSet): PType = - result = t - if t == nil: return - if ContainsOrIncl(cl, t.id): return - case t.kind - of tyGenericBody: - result = newTypeS(tyGenericInvokation, c) - addSon(result, t) - for i in countup(0, sonsLen(t) - 2): - if t.sons[i].kind != tyGenericParam: - InternalError("addTypeVarsOfGenericBody") - # do not declare ``TKey`` twice: - #if not ContainsOrIncl(cl, t.sons[i].sym.ident.id): - var s = copySym(t.sons[i].sym) - s.position = sonsLen(genericParams) - if s.typ == nil or s.typ.kind != tyGenericParam: - InternalError("addTypeVarsOfGenericBody 2") - addDecl(c, s) - addSon(genericParams, newSymNode(s)) - addSon(result, t.sons[i]) - of tyGenericInst: - var L = sonsLen(t) - 1 - t.sons[L] = addTypeVarsOfGenericBody(c, t.sons[L], genericParams, cl) - of tyGenericInvokation: - for i in countup(1, sonsLen(t) - 1): - t.sons[i] = addTypeVarsOfGenericBody(c, t.sons[i], genericParams, cl) - else: - for i in countup(0, sonsLen(t) - 1): - t.sons[i] = addTypeVarsOfGenericBody(c, t.sons[i], genericParams, cl) - -proc paramType(c: PContext, n, genericParams: PNode, cl: var TIntSet): PType = - result = semTypeNode(c, n, nil) - if genericParams != nil and sonsLen(genericParams) == 0: - result = addTypeVarsOfGenericBody(c, result, genericParams, cl) - proc addParamOrResult(c: PContext, param: PSym, kind: TSymKind) = if kind == skMacro and param.typ.kind in {tyTypeDesc, tyExpr, tyStmt}: let nn = getSysSym"PNimrodNode" @@ -520,13 +484,33 @@ proc addParamOrResult(c: PContext, param: PSym, kind: TSymKind) = else: addDecl(c, param) -proc paramTypeClass(c: PContext, paramType: PType, procKind: TSymKind): PType = +proc paramTypeClass(c: PContext, paramType: PType, procKind: TSymKind): + tuple[typ: PType, id: PIdent] = + # if typ is not-nil, the param should be turned into a generic param + # if id is not nil, the generic param will bind just once (see below) case paramType.kind: of tyExpr: + # proc(a, b: expr) if procKind notin {skTemplate, skMacro}: - result = newTypeS(tyGenericParam, c) + result.typ = newTypeS(tyGenericParam, c) + of tyDistinct: + # type T1 = distinct expr + # type S1 = distinct Sortable + # proc x(a, b: T1, c, d: S1) + # This forces bindOnce behavior for the type class, equivalent to + # proc x[T, S](a, b: T, c, d: S) + result = paramTypeClass(c, paramType.lastSon, procKind) + result.id = paramType.sym.name + of tyGenericBody: + # type Foo[T] = object + # proc x(a: Foo, b: Foo) + result.typ = newTypeS(tyTypeClass, c) + result.typ.addSon(paramType) + result.id = paramType.sym.name # bindOnce by default + of tyTypeClass: + result.typ = copyType(paramType, getCurrOwner(), false) else: nil - + proc semProcTypeNode(c: PContext, n, genericParams: PNode, prev: PType, kind: TSymKind): PType = var @@ -555,14 +539,8 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, hasDefault = a.sons[length-1].kind != nkEmpty if hasType: - typ = paramType(c, a.sons[length-2], genericParams, cl) - if c.filename.endsWith"nimdbg.nim" and typ != nil: - echo("PARAM TYPE ", typ.kind, " ") - if genericParams != nil: - echo genericParams.info.toFileLineCol - debug typ - #if matchType(typ, [(tyVar, 0)], tyGenericInvokation): - # debug a.sons[length-2][0][1] + typ = semTypeNode(c, a.sons[length-2], nil) + if hasDefault: def = semExprWithType(c, a.sons[length-1]) # check type compability between def.typ and typ: @@ -580,42 +558,46 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, if skipTypes(typ, {tyGenericInst}).kind == tyEmpty: continue for j in countup(0, length-3): var arg = newSymS(skParam, a.sons[j], c) - let typeClass = paramTypeClass(c, typ, kind) + var endingType = typ + var (typeClass, paramTypId) = paramTypeClass(c, typ, kind) if typeClass != nil: - let typeClassParamId = getIdent(arg.name.s & ":type") + if paramTypId == nil: paramTypId = getIdent(arg.name.s & ":type") if genericParams == nil: # genericParams is nil when the proc is being instantiated # the resolved type will be in scope then - var s = SymtabGet(c.tab, typeClassParamId) - arg.typ = s.typ + endingType = SymtabGet(c.tab, paramTypId).AssertNotNull.typ else: - var s = newSym(skType, typeClassParamId, getCurrOwner()) - s.typ = typeClass - s.typ.sym = s - s.position = genericParams.len - genericParams.addSon(newSymNode(s)) - arg.typ = s.typ - else: - arg.typ = typ + block addImplicitGeneric: + # is this a bindOnce type class already present in the param list? + for i in countup(0, genericParams.len - 1): + if genericParams.sons[i].sym.name == paramTypId: + endingType = genericParams.sons[i].typ + break addImplicitGeneric + var s = newSym(skType, paramTypId, getCurrOwner()) + s.typ = typeClass + s.typ.sym = s + s.position = genericParams.len + genericParams.addSon(newSymNode(s)) + endingType = typeClass + + arg.typ = endingType arg.position = counter inc(counter) if def != nil and def.kind != nkEmpty: arg.ast = copyTree(def) if ContainsOrIncl(check, arg.name.id): LocalError(a.sons[j].info, errAttemptToRedefine, arg.name.s) addSon(result.n, newSymNode(arg)) - addSon(result, typ) + addSon(result, endingType) addParamOrResult(c, arg, kind) - if n.sons[0].kind != nkEmpty: - var r = paramType(c, n.sons[0], genericParams, cl) + if n.sons[0].kind != nkEmpty: + var r = semTypeNode(c, n.sons[0], nil) # turn explicit 'void' return type into 'nil' because the rest of the # compiler only checks for 'nil': if skipTypes(r, {tyGenericInst}).kind != tyEmpty: result.sons[0] = r res.typ = result.sons[0] - #if matchType(result, [(tyProc, 1), (tyVar, 0)], tyGenericInvokation): - # debug result proc semStmtListType(c: PContext, n: PNode, prev: PType): PType = checkMinSonsLen(n, 1) @@ -707,9 +689,23 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = if sonsLen(n) == 1: result = semTypeNode(c, n.sons[0], prev) else: GlobalError(n.info, errTypeExpected) of nkCallKinds: - # expand macros and templates - var expandedSym = expectMacroOrTemplateCall(c, n) - result = semExpandToType(c, n, expandedSym) + let op = n.sons[0].ident.id + if op in {ord(wAnd), ord(wOr)}: + var + t1 = semTypeNode(c, n.sons[1], nil) + t2 = semTypeNode(c, n.sons[2], nil) + + if t1 == nil: GlobalError(n.sons[1].info, errTypeExpected) + elif t2 == nil: GlobalError(n.sons[2].info, errTypeExpected) + else: + result = newTypeS(tyTypeClass, c) + result.addSon(t1) + result.addSon(t2) + result.flags.incl(if op == ord(wAnd): tfAll else: tfAny) + else: + # expand macros and templates + var expandedSym = expectMacroOrTemplateCall(c, n) + result = semExpandToType(c, n, expandedSym) of nkWhenStmt: var whenResult = semWhen(c, n, false) if whenResult.kind == nkStmtList: whenResult.kind = nkStmtListType diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 903506a72a..0c44205de7 100755 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -202,6 +202,7 @@ proc ReplaceTypeVarsT*(cl: var TReplTypeVars, t: PType): PType = result = t if t == nil: return case t.kind + of tyTypeClass: nil of tyGenericParam: result = lookupTypeVar(cl, t) if result.kind == tyGenericInvokation: diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 74131c21dc..767bf3cb83 100755 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -208,9 +208,15 @@ proc tupleRel(mapping: var TIdTable, f, a: PType): TTypeRelation = var y = a.n.sons[i].sym if x.name.id != y.name.id: return isNone -proc constraintRel(mapping: var TIdTable, f, a: PType): TTypeRelation = +proc matchTypeClass(mapping: var TIdTable, f, a: PType): TTypeRelation = result = isNone - if f.kind == a.kind: result = isGeneric + let son = f.sons[0] + if son.kind == a.kind: + result = isGeneric + elif son.kind == tyGenericBody: + if a.kind == tyGenericInst and a.sons[0] == son: + result = isGeneric + put(mapping, f, a) proc procTypeRel(mapping: var TIdTable, f, a: PType): TTypeRelation = proc inconsistentVarTypes(f, a: PType): bool {.inline.} = @@ -261,7 +267,8 @@ proc typeRel(mapping: var TIdTable, f, a: PType): TTypeRelation = assert(a != nil) if a.kind == tyGenericInst and skipTypes(f, {tyVar}).kind notin { - tyGenericBody, tyGenericInvokation, tyGenericParam}: + tyGenericBody, tyGenericInvokation, + tyGenericParam, tyTypeClass }: return typeRel(mapping, f, lastSon(a)) if a.kind == tyVar and f.kind != tyVar: return typeRel(mapping, f, a.sons[0]) @@ -341,7 +348,7 @@ proc typeRel(mapping: var TIdTable, f, a: PType): TTypeRelation = if result < isGeneric: result = isNone else: nil of tyTypeClass: - result = constraintRel(mapping, f.sons[0], a) + result = matchTypeClass(mapping, f, a) of tyOrdinal: if isOrdinalType(a): var x = if a.kind == tyOrdinal: a.sons[0] else: a @@ -414,7 +421,7 @@ proc typeRel(mapping: var TIdTable, f, a: PType): TTypeRelation = of tyGenericBody: let ff = lastSon(f) if ff != nil: result = typeRel(mapping, ff, a) - of tyGenericInvokation: + of tyGenericInvokation: assert(f.sons[0].kind == tyGenericBody) if a.kind == tyGenericInvokation: #InternalError("typeRel: tyGenericInvokation -> tyGenericInvokation") diff --git a/compiler/types.nim b/compiler/types.nim index 8865735ab8..5b3a1e2895 100755 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -409,7 +409,7 @@ proc TypeToString(typ: PType, prefer: TPreferedDesc = preferName): string = of tySequence: result = "seq[" & typeToString(t.sons[0]) & ']' of tyOrdinal: - result = "ordinal" + result = "ordinal[" & typeToString(t.sons[0]) & ']' of tySet: result = "set[" & typeToString(t.sons[0]) & ']' of tyOpenArray: diff --git a/lib/system.nim b/lib/system.nim index 6583e4f154..e1b82b6bb8 100755 --- a/lib/system.nim +++ b/lib/system.nim @@ -2173,7 +2173,7 @@ proc InstantiationInfo*(index = -1): tuple[filename: string, line: int] {. ## This is only useful for advanced meta programming. See the implementation ## of `assert` for an example. -proc raiseAssert(msg: string) {.noinline.} = +proc raiseAssert*(msg: string) {.noinline.} = raise newException(EAssertionFailed, msg) template assert*(cond: bool, msg = "") = @@ -2182,7 +2182,7 @@ template assert*(cond: bool, msg = "") = ## raises an ``EAssertionFailure`` exception. However, the compiler may ## not generate any code at all for ``assert`` if it is advised to do so. ## Use ``assert`` for debugging purposes only. - bind raiseAssert, InstantiationInfo + bind InstantiationInfo when compileOption("assertions"): {.line.}: if not cond: @@ -2191,11 +2191,33 @@ template assert*(cond: bool, msg = "") = template doAssert*(cond: bool, msg = "") = ## same as `assert` but is always turned on and not affected by the ## ``--assertions`` command line switch. - bind raiseAssert, InstantiationInfo + bind InstantiationInfo {.line: InstantiationInfo().}: if not cond: raiseAssert(astToStr(cond) & ' ' & msg) +template onFailedAssert*(msg: expr, code: stmt): stmt = + ## Sets an assertion failure handler that will intercept any assert statements + ## following `onFailedAssert` in the current lexical scope. + ## Can be defined multiple times in a single function. + ## + ## .. code-block:: nimrod + ## + ## proc example(x: int): TErrorCode = + ## onFailedAssert(msg): + ## log msg + ## return E_FAIL + ## + ## assert(...) + ## + ## onFailedAssert(msg): + ## raise newException(EMyException, msg) + ## + ## assert(...) + ## + template raiseAssert(msgIMPL: string): stmt = + let `msg` = msgIMPL + code proc shallow*[T](s: var seq[T]) {.noSideEffect, inline.} = ## marks a sequence `s` as `shallow`:idx:. Subsequent assignments will not diff --git a/tests/compile/ttypeclasses.nim b/tests/compile/ttypeclasses.nim new file mode 100644 index 0000000000..e22ede1dc7 --- /dev/null +++ b/tests/compile/ttypeclasses.nim @@ -0,0 +1,34 @@ +type + TFoo[T] = object + val: T + + T1 = distinct expr + T2 = distinct expr + +proc takesExpr(x, y) = + echo x, y + +proc same(x, y: T1) = + echo x, y + +proc takesFoo(x, y: TFoo) = + echo x.val, y.val + +proc takes2Types(x,y: T1, z: T2) = + echo x, y, z + +takesExpr(1, 2) +takesExpr(1, "xxx") +takesExpr[bool, int](true, 0) + +same(1, 2) +same("test", "test") + +var f: TFoo[int] +f.val = 10 + +takesFoo(f, f) + +takes2Types(1, 1, "string") +takes2Types[string, int]("test", "test", 1) + diff --git a/tests/run/uexpr.nim b/tests/run/utypeclasses.nim similarity index 100% rename from tests/run/uexpr.nim rename to tests/run/utypeclasses.nim