diff options
47 files changed, 2038 insertions, 345 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index 26305cf3b..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 @@ -354,44 +355,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 +409,7 @@ type # sons[1]: field type # .n: nkDotExpr storing the field name - tyVoid #\ + tyVoid # now different from tyEmpty, hurray! static: @@ -420,6 +429,7 @@ const tyAnd, tyOr, tyNot, tyAnything} tyMetaTypes* = {tyGenericParam, tyTypeDesc, tyExpr} + tyTypeClasses + tyUserTypeClasses* = {tyUserTypeClass, tyUserTypeClassInst} type TTypeKinds* = set[TTypeKind] @@ -463,6 +473,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 @@ -482,6 +494,9 @@ type tfHasStatic 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 @@ -1036,6 +1051,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 @@ -1062,6 +1080,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 @@ -1278,6 +1299,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 @@ -1561,7 +1584,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.. <n.len: yield n.sons[i] + for i in 0.. <n.safeLen: yield n.sons[i] iterator pairs*(n: PNode): tuple[i: int, n: PNode] = for i in 0.. <n.len: yield (i, n.sons[i]) @@ -1604,6 +1627,16 @@ proc toObject*(typ: PType): PType = if result.kind == tyRef: result = result.lastSon +proc findUnresolvedStatic*(n: PNode): PNode = + if n.kind == nkSym and n.typ.kind == tyStatic and n.typ.n == nil: + return n + + for son in n: + let n = son.findUnresolvedStatic + if n != nil: return n + + return nil + when false: proc containsNil*(n: PNode): bool = # only for debugging 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/ccgtypes.nim b/compiler/ccgtypes.nim index 8fdd97428..9915ad355 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -164,8 +164,11 @@ 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: + tyTypeDesc, tyAlias, tyInferred: result = mapType(lastSon(typ)) of tyEnum: if firstOrd(typ) < 0: @@ -787,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/ccgutils.nim b/compiler/ccgutils.nim index ff8f768bd..c37a8fcdb 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: @@ -108,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/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/jsgen.nim b/compiler/jsgen.nim index 9c50a4fc9..d64e4f7dd 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/msgs.nim b/compiler/msgs.nim index 2db3646b5..3a97f1ed2 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -109,6 +109,7 @@ type errXCannotBeClosure, errXMustBeCompileTime, errCannotInferTypeOfTheLiteral, errCannotInferReturnType, + errCannotInferStaticParam, errGenericLambdaNotAllowed, errProcHasNoConcreteType, errCompilerDoesntSupportTarget, @@ -132,7 +133,7 @@ type hintConditionAlwaysTrue, hintName, hintPattern, hintExecuting, hintLinking, hintDependency, hintSource, hintStackTrace, hintGCStats, - hintUser + hintUser, hintUserRaw const MsgKindToStr*: array[TMsgKind, string] = [ @@ -373,6 +374,7 @@ const errXMustBeCompileTime: "'$1' can only be used in compile-time context", errCannotInferTypeOfTheLiteral: "cannot infer the type of the $1", errCannotInferReturnType: "cannot infer the return type of the proc", + errCannotInferStaticParam: "cannot infer the value of the static param `$1`", errGenericLambdaNotAllowed: "A nested proc can have generic parameters only when " & "it is used as an operand to another routine and the types " & "of the generic paramers can be inferred from the expected signature.", @@ -432,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", @@ -447,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 @@ -497,7 +500,6 @@ type TErrorOutput* = enum eStdOut eStdErr - eInMemory TErrorOutputs* = set[TErrorOutput] @@ -651,6 +653,12 @@ 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 + proc suggestWriteln*(s: string) = if eStdOut in errorOutputs: if isNil(writelnHook): @@ -804,10 +812,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): @@ -922,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 @@ -990,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/options.nim b/compiler/options.nim index 6372cddac..c4a57f41c 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 @@ -73,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/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..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 = @@ -1793,8 +1801,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/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/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) 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/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/semcall.nim b/compiler/semcall.nim index ffa940291..d6852859b 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, @@ -55,7 +58,8 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, initialBinding: PNode, filter: TSymKinds, best, alt: var TCandidate, - errors: var CandidateErrors) = + errors: var CandidateErrors, + diagnosticsFlag = false) = var o: TOverloadIter var sym = initOverloadIter(o, c, headSymbol) var scope = o.lastOverloadScope @@ -68,8 +72,8 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, 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) + initCandidate(c, best, sym, initialBinding, scope, diagnosticsFlag) + initCandidate(c, alt, sym, initialBinding, scope, diagnosticsFlag) best.state = csNoMatch break else: @@ -82,14 +86,9 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, scope = o.lastOverloadScope continue determineType(c, sym) - initCandidate(c, z, sym, initialBinding, scope) + initCandidate(c, z, sym, initialBinding, scope, diagnosticsFlag) if c.currentScope.symbols.counter == counterInitial or syms != nil: matches(c, n, orig, z) - if errors != nil: - errors.safeAdd((sym, int z.mutabilityProblem)) - if z.errors != nil: - for err in z.errors: - errors.add(err) if z.state == csMatch: # little hack so that iterators are preferred over everything else: if sym.kind == skIterator: inc(z.exactMatches, 200) @@ -99,10 +98,16 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, 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 + elif errors != nil or z.diagnostics != nil: + 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. - syms = initCandidateSymbols(c, headSymbol, initialBinding, filter, best, alt, o) + syms = initCandidateSymbols(c, headSymbol, initialBinding, filter, + best, alt, o, diagnosticsFlag) if syms == nil: sym = nextOverloadIter(o, c, headSymbol) scope = o.lastOverloadScope @@ -114,17 +119,9 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, else: break -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: - # fail fast: - globalError(n.info, errTypeMismatch, "") - if errors.isNil or errors.len == 0: - localError(n.info, errExprXCannotBeCalled, n[0].renderTree) - return - +proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors): + (TPreferedDesc, string) = + var prefer = preferName # to avoid confusing errors like: # got (SslPtr, SocketHandle) # but expected one of: @@ -132,11 +129,9 @@ proc notFoundError*(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) - - var prefer = preferName - for err, mut 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: @@ -147,26 +142,40 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) = prefer = preferModuleInfo break - # now use the information stored in 'prefer' to produce a nice error message: - var result = msgKindToString(errTypeMismatch) - add(result, describeArgs(c, n, 1, prefer)) - add(result, ')') var candidates = "" - for err, mut in items(errors): - if err.kind in routineKinds and err.ast != nil: - add(candidates, renderTree(err.ast, - {renderNoBody, renderNoComments,renderNoPragmas})) + 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") + 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) + +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 errorOutputs == {}: + # 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) - 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 = @[] @@ -175,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)) + 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) @@ -183,7 +194,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 @@ -197,7 +208,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 @@ -263,18 +274,13 @@ 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): 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: @@ -289,7 +295,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 @@ -378,23 +383,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, hintUserRaw, + "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)) @@ -450,6 +472,7 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = 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) @@ -473,7 +496,7 @@ proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym = x = t.baseOfDistinct call.add(newNodeIT(nkEmpty, fn.info, x)) if hasDistinct: - var resolved = semOverloadedCall(c, call, call, {fn.kind}) + var resolved = semOverloadedCall(c, call, call, {fn.kind}, {}) if resolved != nil: result = resolved.sons[0].sym if not compareTypes(result.typ.sons[0], fn.typ.sons[0], dcEqIgnoreDistinct): @@ -481,4 +504,3 @@ proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym = elif result.magic in {mArrPut, mArrGet}: # cannot borrow these magics for now result = nil - diff --git a/compiler/semdata.nim b/compiler/semdata.nim index ef23e40f2..023b85802 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -46,9 +46,10 @@ type TExprFlag* = enum efLValue, efWantIterator, efInTypeof, - efWantStmt, efAllowStmt, efDetermineType, + efWantStmt, efAllowStmt, efDetermineType, efExplain, efAllowDestructor, efWantValue, efOperand, efNoSemCheck, - efNoProcvarCheck, efNoEvaluateGeneric, efInCall, efFromHlo + efNoProcvarCheck, efNoEvaluateGeneric, efInCall, efFromHlo, + TExprFlags* = set[TExprFlag] TTypeAttachedOp* = enum @@ -84,12 +85,12 @@ type libs*: seq[PLib] # all libs used by this module semConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # for the pragmas semExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.} - semTryExpr*: proc (c: PContext, n: PNode,flags: TExprFlags = {}): PNode {.nimcall.} + semTryExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.} semTryConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} semOperand*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.} semConstBoolExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # XXX bite the bullet semOverloadedCall*: proc (c: PContext, n, nOrig: PNode, - filter: TSymKinds): PNode {.nimcall.} + filter: TSymKinds, flags: TExprFlags): PNode {.nimcall.} semTypeNode*: proc(c: PContext, n: PNode, prev: PType): PType {.nimcall.} semInferredLambda*: proc(c: PContext, pt: TIdTable, n: PNode): PNode semGenerateInstance*: proc (c: PContext, fn: PSym, pt: TIdTable, @@ -230,6 +231,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 +250,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) @@ -259,7 +274,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) @@ -303,16 +319,14 @@ proc makeRangeWithStaticExpr*(c: PContext, n: PNode): PType = let intType = getSysType(tyInt) result = newTypeS(tyRange, c) result.sons = @[intType] + if n.typ != nil and 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 = - # 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 +template rangeHasUnresolvedStatic*(t: PType): bool = + 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 80b844053..5f9263645 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: @@ -301,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} @@ -318,16 +319,18 @@ 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 + var t2 = rhsOrigType.skipTypes({tyTypeDesc}) 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") @@ -347,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} @@ -625,6 +628,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)) @@ -640,10 +644,10 @@ proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode, # for typeof support. # for ``type(countup(1,3))``, see ``tests/ttoseq``. result = semOverloadedCall(c, n, nOrig, - {skProc, skMethod, skConverter, skMacro, skTemplate, skIterator}) + {skProc, skMethod, skConverter, skMacro, skTemplate, skIterator}, flags) else: result = semOverloadedCall(c, n, nOrig, - {skProc, skMethod, skConverter, skMacro, skTemplate}) + {skProc, skMethod, skConverter, skMacro, skTemplate}, flags) if result != nil: if result.sons[0].kind != nkSym: @@ -751,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 @@ -912,20 +916,42 @@ const proc readTypeParameter(c: PContext, typ: PType, paramName: PIdent, info: TLineInfo): PNode = - let ty = if typ.kind == tyGenericInst: 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 + if typ.kind in {tyUserTypeClass, tyUserTypeClassInst}: + for statement in typ.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[0].sym).linkTo(foundType), info) + + of nkConstSection: + for def in statement: + if def[0].sym.name.id == paramName.id: + return def[2] + else: - let foundTyp = makeTypeDesc(c, rawTyp) - return newSymNode(copySym(tParam.sym).linkTo(foundTyp), info) - #echo "came here: returned nil" + 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 proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = let s = getGenSym(c, sym) @@ -1080,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) @@ -1097,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) @@ -1107,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": @@ -1151,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]): @@ -1718,7 +1757,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = let oldOwnerLen = len(c.graph.owners) let oldGenerics = c.generics let oldErrorOutputs = errorOutputs - errorOutputs = {} + if efExplain notin flags: errorOutputs = {} let oldContextLen = msgs.getInfoContextLen() let oldInGenericContext = c.inGenericContext @@ -1731,8 +1770,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 @@ -1746,8 +1784,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: @@ -1814,7 +1850,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) @@ -2327,8 +2363,20 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = of nkCurlyExpr: result = semExpr(c, buildOverloadedSubscripts(n, getIdent"{}"), flags) of nkPragmaExpr: - # which pragmas are allowed for expressions? `likely`, `unlikely` - internalError(n.info, "semExpr() to implement") # XXX: to implement + var + expr = n[0] + pragma = n[1] + pragmaName = considerQuotedIdent(pragma[0]) + flags = flags + + case whichKeyword(pragmaName) + of wExplain: + flags.incl efExplain + else: + # what other pragmas are allowed for expressions? `likely`, `unlikely` + invalidPragma(n) + + result = semExpr(c, n[0], flags) of nkPar: case checkPar(n) of paNone: result = errorNode(c, n) 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/semmagic.nim b/compiler/semmagic.nim index 5eed1e702..3e1989eaf 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 @@ -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/semstmts.nim b/compiler/semstmts.nim index f4efcc0ff..5f025f943 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -501,6 +501,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) @@ -1554,6 +1556,16 @@ proc usesResult(n: PNode): bool = for c in n: if usesResult(c): return true +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, + "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 = # these must be last statements in a block: const @@ -1605,10 +1617,21 @@ 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], flags) + 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].kind == nkSym and + expr[0].sym.name.s == "==": + if expr[1].typ.isUnresolvedStatic: + inferConceptStaticParam(c, expr[1], expr[2]) + continue + elif expr[2].typ.isUnresolvedStatic: + inferConceptStaticParam(c, expr[2], expr[1]) + continue + let verdict = semConstExpr(c, n[i]) if verdict.intVal == 0: localError(result.info, "type class predicate failed") @@ -1632,8 +1655,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/semtypes.nim b/compiler/semtypes.nim index e86b527d6..422d2f0fa 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] @@ -185,16 +185,21 @@ 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]): result.n.addSon makeStaticExpr(c, range[i]) + result.flags.incl tfUnresolved else: result.n.addSon semConstExpr(c, range[i]) @@ -227,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: @@ -765,6 +771,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, let owner = if typeClass.sym != nil: typeClass.sym else: getCurrOwner(c) var s = newSym(skType, finalTypId, owner, info) + if sfExplain in owner.flags: s.flags.incl sfExplain if typId == nil: s.flags.incl(sfAnon) s.linkTo(typeClass) typeClass.flags.incl tfImplicitTypeParam @@ -843,9 +850,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) @@ -883,12 +890,13 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, for i in 1 .. <paramType.len: let lifted = liftingWalk(paramType.sons[i]) if lifted != nil: paramType.sons[i] = lifted - when false: + + if paramType.base.lastSon.kind == tyUserTypeClass: let expanded = instGenericContainer(c, info, paramType, 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: @@ -1223,6 +1231,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, errTypeExpected) + result = errorSym(c, n) + proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = nil when defined(nimsuggest): @@ -1237,6 +1252,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: @@ -1312,7 +1328,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) @@ -1344,6 +1362,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) @@ -1403,7 +1423,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/semtypinst.nim b/compiler/semtypinst.nim index 75dffb67f..fddcc7a24 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: @@ -144,7 +145,7 @@ proc reResolveCallsWithTypedescParams(cl: var TReplTypeVars, n: PNode): PNode = if isTypeParam(n[i]): needsFixing = true if needsFixing: n.sons[0] = newSymNode(n.sons[0].sym.owner) - return cl.c.semOverloadedCall(cl.c, n, n, {skProc}) + return cl.c.semOverloadedCall(cl.c, n, n, {skProc}, {}) for i in 0 .. <n.safeLen: n.sons[i] = reResolveCallsWithTypedescParams(cl, n[i]) @@ -403,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)) @@ -444,10 +447,10 @@ 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: + of tyGenericInst, tyUserTypeClassInst: bailout() result = instCopyType(cl, t) idTablePut(cl.localCache, t, result) @@ -501,8 +504,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 164fd0999..ff7b0ae72 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -22,7 +22,13 @@ type TCandidateState* = enum csEmpty, csMatch, csNoMatch - CandidateErrors* = seq[(PSym,int)] + CandidateError* = object + sym*: PSym + unmatchedVarParam*: int + diagnostics*: seq[string] + + CandidateErrors* = seq[CandidateError] + TCandidate* = object c*: PContext exactMatches*: int # also misused to prefer iters over procs @@ -49,11 +55,20 @@ 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. + diagnostics*: seq[string] # when this is not nil, the matching process + # will collect extra diagnostics that will be + # displayed to the user. + # triggered when overload resolution fails + # or when the explain pragma is used. may be + # triggered with an idetools command in the + # future. mutabilityProblem*: uint8 # tyVar mismatch inheritancePenalty: int # to prefer closest father object type - errors*: CandidateErrors # additional clarifications to be displayed to the - # user if overload resolution fails - + TTypeRelation* = enum # order is important! isNone, isConvertible, isIntConv, @@ -101,7 +116,7 @@ proc put(c: var TCandidate, key, val: PType) {.inline.} = idTablePut(c.bindings, key, val.skipIntLit) proc initCandidate*(ctx: PContext, c: var TCandidate, callee: PSym, - binding: PNode, calleeScope = -1) = + binding: PNode, calleeScope = -1, diagnostics = false) = initCandidateAux(ctx, c, callee.typ) c.calleeSym = callee if callee.kind in skProcKinds and calleeScope == -1: @@ -116,9 +131,9 @@ proc initCandidate*(ctx: PContext, c: var TCandidate, callee: PSym, c.calleeScope = 1 else: c.calleeScope = calleeScope + c.diagnostics = if diagnostics: @[] else: nil c.magic = c.calleeSym.magic initIdTable(c.bindings) - c.errors = nil if binding != nil and callee.kind in routineKinds: var typeParams = callee.ast[genericParamsPos] for i in 1..min(sonsLen(typeParams), sonsLen(binding)-1): @@ -572,19 +587,22 @@ proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} = result = isNone proc matchUserTypeClass*(c: PContext, m: var TCandidate, - ff, a: PType): TTypeRelation = - var body = ff.skipTypes({tyUserTypeClassInst}) + ff, a: PType): PType = + var + typeClass = ff.skipTypes({tyUserTypeClassInst}) + body = typeClass.n[3] if c.inTypeClass > 4: - localError(body.n[3].info, $body.n[3] & " too nested for type matching") - return isNone + localError(body.info, $body & " too nested for type matching") + return nil openScope(c) inc c.inTypeClass - defer: dec c.inTypeClass closeScope(c) + var typeParams: seq[(PSym, PType)] + if ff.kind == tyUserTypeClassInst: for i in 1 .. <(ff.len - 1): var @@ -593,44 +611,104 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, param: PSym template paramSym(kind): untyped = - newSym(kind, typeParamName, body.sym, body.sym.info) - - case typ.kind - of tyStatic: - param = paramSym skConst - param.typ = typ.base - param.ast = typ.n - of tyUnknown: - param = paramSym skVar - param.typ = typ - else: - param = paramSym skType - param.typ = makeTypeDesc(c, typ) - + newSym(kind, typeParamName, typeClass.sym, typeClass.sym.info) + + 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) - #echo "A ", param.name.s, " ", typeToString(param.typ), " ", param.kind - for param in body.n[0]: + for param in typeClass.n[0]: var 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) + if modifier == tyTypeDesc: dummyType.flags.incl tfExplicit 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, typeClass.sym, typeClass.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) - if checkedBody == nil: return isNone - return isGeneric + var + oldWriteHook: type(writelnHook) + diagnostics: seq[string] + errorPrefix: string + flags: TExprFlags = {} + collectDiagnostics = m.diagnostics != nil or + sfExplain in typeClass.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 = @[] + flags = {efExplain} + writelnHook = proc (s: string) = + if errorPrefix == nil: errorPrefix = typeClass.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: + 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. + # 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, p[1], p[0].typ) + + if ff.kind == tyUserTypeClassInst: + result = generateTypeInstance(c, m.bindings, typeClass.sym.info, ff) + else: + result = copyType(ff, ff.owner, true) + + result.n = checkedBody proc shouldSkipDistinct(rules: PNode, callIdent: PIdent): bool = if rules.kind == nkWith: @@ -649,16 +727,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, 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 @@ -689,8 +876,49 @@ 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 and + tfExplicit notin aOrig.flags + + aOrig = if useTypeLoweringRuleInTypeClass: + aOrig.skipTypes({tyTypeDesc, tyFieldAccessor}) + else: + aOrig + + if aOrig.kind == tyInferred: + 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 @@ -702,6 +930,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 @@ -807,6 +1038,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: @@ -814,28 +1046,14 @@ 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): - 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) - 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: + return inferStaticsInRange(c, aRange, f) elif lengthOrd(fRange) != lengthOrd(a): result = isNone else: discard @@ -1111,11 +1329,17 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = else: return isNone - of tyUserTypeClass, tyUserTypeClassInst: - considerPreviousT: - result = matchUserTypeClass(c.c, c, f, aOrig) - if result == isGeneric: - put(c, f, a) + of tyUserTypeClassInst, tyUserTypeClass: + if f.isResolvedUserTypeClass: + result = typeRel(c, f.lastSon, a) + else: + var matched = matchUserTypeClass(c.c, c, f, aOrig) + if matched != nil: + bindConcreteTypeToUserTypeClass(matched, a) + put(c, f, matched) + result = isGeneric + else: + result = isNone of tyCompositeTypeClass: considerPreviousT: @@ -1212,6 +1436,17 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = # XXX endless recursion? #result = typeRel(c, prev, aOrig) result = isNone + + of tyInferred: + 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: @@ -1355,12 +1590,12 @@ proc incMatches(m: var TCandidate; r: TTypeRelation; convMatch = 1) = of isEqual: inc(m.exactMatches) of isNone: discard -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 tfHasStatic in fMaybeStatic.flags: @@ -1370,15 +1605,15 @@ 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: + a.n == nil and + tfGenericTypeParam notin a.flags: return newNodeIT(nkType, argOrig.info, makeTypeFromExpr(c, arg)) else: var evaluated = c.semTryConstExpr(c, arg) @@ -1386,9 +1621,8 @@ 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 a = argType var r = typeRel(m, f, a) if r != isNone and m.calleeSym != nil and @@ -1830,6 +2064,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 f4ef75094..3f84548a1 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} @@ -182,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): @@ -410,12 +411,19 @@ const "unused0", "unused1", "unused2", "varargs[$1]", "unused", "Error Type", "BuiltInTypeClass", "UserTypeClass", - "UserTypeClassInst", "CompositeTypeClass", + "UserTypeClassInst", "CompositeTypeClass", "inferred", "and", "or", "not", "any", "static", "TypeFromExpr", "FieldAccessor", "void"] 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") @@ -460,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: @@ -476,6 +485,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 +984,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 +1133,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 @@ -1305,14 +1320,20 @@ 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: + 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 @@ -1333,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/vmdeps.nim b/compiler/vmdeps.nim index d684c4c32..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,15 +285,19 @@ 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) 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 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/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/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 diff --git a/doc/manual/generics.txt b/doc/manual/generics.txt index c1c6467e7..87fcb7828 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 @@ -148,8 +149,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 +162,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 +213,421 @@ 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 -to encode usage protocols that do not reveal implementation details. +default parameters are still applied in the concept body, it's also possible +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. + + +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 + + import typetraits + + 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 + + type TransposedType = stripGenericParams(M)[C, R, T] + + AnySquareMatrix*[N: static[int], T] = AnyMatrix[N, N, T] + + AnyTransform3D* = AnyMatrix[4, 4, float] + + proc transposed*(m: AnyMatrix): m.TransposedType = + for r in 0 .. <m.R: + for c in 0 .. <m.C: + result[r, c] = m[c, r] + + proc determinant*(m: AnySquareMatrix): int = + ... + + proc setPerspectiveProjection*(m: AnyTransform3D) = + ... + + -------------- + ### matrix.nim + + type + Matrix*[M, N: static[int]; T] = object + data: array[M*N, T] + + proc `[]`*(M: Matrix; m, n: int): M.T = + M.data[m * M.N + n] + + proc `[]=`*(M: var Matrix; m, n: int; v: M.T) = + M.data[m * M.N + n] = v + + # Adapt the Matrix type to the concept's requirements + template Rows*(M: type Matrix): expr = M.M + template Cols*(M: type Matrix): expr = M.N + template ValueType*(M: type Matrix): typedesc = M.T + + ------------- + ### usage.nim + + import matrix, matrixalgo + + var + m: Matrix[3, 3, int] + projectionMatrix: Matrix[4, 4, float] + + echo m.transposed.determinant + setPerspectiveProjection projectionMatrix + +When the concept type is matched against a concrete type, the unbound type +parameters are inferred from the body of the concept in a way that closely +resembles the way generic parameters of callable symbols are inferred on +call sites. + +Unbound types can appear both as params to calls such as `s.push(T)` and +on the right-hand side of the ``is`` operator in cases such as `x.pop is T` +and `x.data is seq[T]`. + +Unbound static params will be inferred from expressions involving the `==` +operator and also when types dependent on them are being matched: + +.. code-block:: nim + type + 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. + +Just like in regular type classes, Nim discriminates between ``bind once`` +and ``bind many`` types when matching the concept. You can add the ``distinct`` +modifier to any of the otherwise inferable types to get a type that will be +matched without permanently inferring it. This may be useful when you need +to match several procs accepting the same wide class of types: + +.. code-block:: nim + type + Enumerable[T] = concept e + for v in e: + v is T + + type + MyConcept = concept o + # this could be inferred to a type such as Enumerable[int] + o.foo is distinct Enumerable + + # this could be inferred to a different type such as Enumerable[float] + o.bar is distinct Enumerable + + # it's also possible to give an alias name to a `bind many` type class + type Enum = distinct Enumerable + o.baz is Enum + +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-defined types: + +.. code-block:: nim + type + MyConcept = concept x + type T1 = auto + x.foo(T1) + x.bar(T1) # both procs must accept the same type + + type T2 = seq[SomeNumber] + x.alpha(T2) + x.omega(T2) # both procs must accept the same type + # 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, +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. +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 + 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 +---------------------- + +All top level constants or types appearing within the concept body are +accessible through the dot operator in procs where the concept was successfully +matched to a concrete type: + +.. code-block:: nim + type + DateTime = concept t1, t2, type T + const Min = T.MinDate + T.Now is T + + t1 < t2 is bool + + type TimeSpan = type(t1 - t2) + TimeSpan * int is TimeSpan + TimeSpan + TimeSpan is TimeSpan + + t1 + TimeSpan is T + + proc eventsJitter(events: Enumerable[DateTime]): float = + var + # this variable will have the inferred TimeSpan type for + # the concrete Date-like value the proc was called with: + averageInterval: DateTime.TimeSpan + + deviation: float + ... + + +Concept refinement +------------------ + +When the matched type within a concept is directly tested against a different +concept, we say that the outer concept is a refinement of the inner concept and +thus it is more-specific. When both concepts are matched in a call during +overload resolution, Nim will assign a higher precedence to the most specific +one. As an alternative way of defining concept refinements, you can use the +object inheritance syntax involving the ``of`` keyword: + +.. code-block:: nim + type + Graph = concept g, type G of EqualyComparable, Copyable + type + VertexType = G.VertexType + EdgeType = G.EdgeType + + VertexType is Copyable + EdgeType is Copyable + + var + v: VertexType + e: EdgeType + + IncidendeGraph = concept of Graph + # symbols such as variables and types from the refined + # concept are automatically in scope: + + g.source(e) is VertexType + g.target(e) is VertexType + + g.outgoingEdges(v) is Enumerable[EdgeType] + + BidirectionalGraph = concept g, type G + # The following will also turn the concept into a refinement when it + # comes to overload resolution, but it doesn't provide the convenient + # symbol inheritance + g is IncidendeGraph + + g.incomingEdges(G.VertexType) is Enumerable[G.EdgeType] + + proc f(g: IncidendeGraph) + proc f(g: BidirectionalGraph) # this one will be preferred if we pass a type + # matching the BidirectionalGraph concept + + +Converter type classes +---------------------- + +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: + +.. code-block:: nim + type + Stringable = concept x + $x is string + return $x + + StringRefValue[CharType] = object + base: ptr CharType + len: int + + StringRef = concept x + # the following would be an overloaded proc for cstring, string, seq and + # other user-defined types, returning either a StringRefValue[char] or + # StringRefValue[wchar] + return makeStringRefValue(x) + + # 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 + proc log(format: static[string], varargs[distinct StringRef]) + + +VTable types +------------ + +Concepts allow Nim to define a great number of algorithms, using only +static polymorphism and without erasing any type information or sacrificing +any execution speed. But when polymorphic collections of objects are required, +the user must use one of the provided type erasure techniques - either common +base types or VTable types. + +VTable types are represented as "fat pointers" storing a reference to an +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 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 ``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 = vtref Enumerable[int] + + MyObject = object + enumerables: seq[IntEnumerable] + streams: seq[OutputStream.vtref] + + proc addEnumerable(o: var MyObject, e: IntEnumerable) = + o.enumerables.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 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 type +in the position of the captured underlying object. -Much like generics, concepts are instantiated exactly -once for each tested type and any static code included within them is also -executed once. +Under these rules, it's possible to obtain a vtable type for a concept with +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. -**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. +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 diff --git a/lib/pure/typetraits.nim b/lib/pure/typetraits.nim index 2c3d872df..55c4bf038 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 + diff --git a/tests/concepts/matrix.nim b/tests/concepts/matrix.nim new file mode 100644 index 000000000..a30cbe4f3 --- /dev/null +++ b/tests/concepts/matrix.nim @@ -0,0 +1,15 @@ +type + Matrix*[M, N: static[int]; T] = object + data: array[M*N, T] + +proc `[]`*(M: Matrix; m, n: int): M.T = + M.data[m * M.N + n] + +proc `[]=`*(M: var Matrix; m, n: int; v: M.T) = + M.data[m * M.N + n] = v + +# Adapt the Matrix type to the concept's requirements +template Rows*(M: type Matrix): expr = M.M +template Cols*(M: type Matrix): expr = M.N +template ValueType*(M: type Matrix): typedesc = M.T + diff --git a/tests/concepts/matrixalgo.nim b/tests/concepts/matrixalgo.nim new file mode 100644 index 000000000..39cf16685 --- /dev/null +++ b/tests/concepts/matrixalgo.nim @@ -0,0 +1,28 @@ +import typetraits + +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 + + type TransposedType = stripGenericParams(M)[C, R, T] + + AnySquareMatrix*[N: static[int], T] = AnyMatrix[N, N, T] + + AnyTransform3D* = AnyMatrix[4, 4, float] + +proc transposed*(m: AnyMatrix): m.TransposedType = + for r in 0 .. <m.R: + for c in 0 .. <m.C: + result[r, c] = m[c, r] + +proc determinant*(m: AnySquareMatrix): int = + return 0 + +proc setPerspectiveProjection*(m: AnyTransform3D) = + discard + 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() + 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() + 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) + diff --git a/tests/concepts/tconcepts_overload_precedence.nim b/tests/concepts/tconcepts_overload_precedence.nim new file mode 100644 index 000000000..9eed6256a --- /dev/null +++ b/tests/concepts/tconcepts_overload_precedence.nim @@ -0,0 +1,69 @@ +discard """ + output: '''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]()) + diff --git a/tests/concepts/texplain.nim b/tests/concepts/texplain.nim new file mode 100644 index 000000000..25a075fd1 --- /dev/null +++ b/tests/concepts/texplain.nim @@ -0,0 +1,120 @@ +discard """ + cmd: "nim c --verbosity:0 --colors:off $file" + nimout: ''' +texplain.nim(99, 10) Hint: Non-matching candidates for e(y) +proc e(i: int): int + +texplain.nim(102, 7) Hint: Non-matching candidates for e(10) +proc e(o: ExplainedConcept): int +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 + +texplain.nim(105, 10) Hint: Non-matching candidates for e(10) +proc e(o: ExplainedConcept): int +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 + +texplain.nim(109, 20) Error: type mismatch: got (NonMatchingType) +but expected one of: +proc e(o: ExplainedConcept): int +texplain.nim(65, 5) ExplainedConcept: type class predicate failed +proc e(i: int): int + +texplain.nim(110, 20) Error: type mismatch: got (NonMatchingType) +but expected one of: +proc r(o: RegularConcept): int +texplain.nim(69, 5) RegularConcept: type class predicate failed +proc r[T](a: SomeNumber; b: T; c: auto) +proc r(i: string): int + +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 + +texplain.nim(119, 2) Error: type mismatch: got (MatchingType) +but expected one of: +proc f(o: NestedConcept) +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)" +""" + +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[T](a: SomeNumber, b: T, c: auto) = discard +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/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/tmatrixconcept.nim b/tests/concepts/tmatrixconcept.nim new file mode 100644 index 000000000..d2597a212 --- /dev/null +++ b/tests/concepts/tmatrixconcept.nim @@ -0,0 +1,81 @@ +discard """ +output: "0\n0\n0" +msg: ''' +R=3 C=3 TE=9 FF=14 FC=20 T=int +R=3 C=3 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 genericMatrixProc[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 implicitMatrixProc(m: Matrix): m.T = + static: + echo "R=", m.Rows, + " C=", m.Cols, + # XXX: fix these + #" TE=", m.TotalElements, + #" FF=", m.FromFoo, + #" FC=", m.FromConst, + " T=", m.T.name + + m[0, 0] + +proc myMatrixProc(x: MyMatrix): MyMatrix.T = genericMatrixProc(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.genericMatrixProc +echo x.implicitMatrixProc + diff --git a/tests/concepts/tmatrixlib.nim b/tests/concepts/tmatrixlib.nim new file mode 100644 index 000000000..a4ab04061 --- /dev/null +++ b/tests/concepts/tmatrixlib.nim @@ -0,0 +1,31 @@ +discard """ +output: "0" +""" + +import matrix, matrixalgo + +import typetraits # XXX: this should be removed + +var m: Matrix[3, 3, int] +var projectionMatrix: Matrix[4, 4, float] + +echo m.transposed.determinant +setPerspectiveProjection projectionMatrix + +template ok(x) = assert x +template no(x) = assert(not x) + +static: + ok projectionMatrix is AnyTransform3D + no m is AnyTransform3D + + type SquareStringMatrix = Matrix[5, 5, string] + + ok SquareStringMatrix is AnyMatrix + ok SquareStringMatrix is AnySquareMatrix + no SquareStringMatrix is AnyTransform3D + + ok Matrix[5, 10, int] is AnyMatrix + no Matrix[7, 15, float] is AnySquareMatrix + no Matrix[4, 4, int] is AnyTransform3D + diff --git a/tests/concepts/tmisc_issues.nim b/tests/concepts/tmisc_issues.nim new file mode 100644 index 000000000..10e072521 --- /dev/null +++ b/tests/concepts/tmisc_issues.nim @@ -0,0 +1,100 @@ +discard """ +output: '''true +true +true +true +p has been called. +p has been called. +implicit generic +generic +false +true +-1''' +""" + +# 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) # true + +# 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 + +# 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()) + +# 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]) + +# 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() + diff --git a/tests/concepts/tstackconcept.nim b/tests/concepts/tstackconcept.nim new file mode 100644 index 000000000..b6ead2c2b --- /dev/null +++ b/tests/concepts/tstackconcept.nim @@ -0,0 +1,63 @@ +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, strutils + +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 + + type ValueType = T + const ValueTypeName = T.name.toUpper + +proc genericAlgorithm[T](s: var Stack[T], y: T) = + 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 + echo "IMPLICIT VALUE TYPE ", s.ValueType.name, " ", Stack.ValueType.name + echo "IMPLICIT VALUE TYPE NAME ", s.ValueTypeName, " ", Stack.ValueTypeName + + 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) + diff --git a/tests/concepts/tusertypeclasses.nim b/tests/concepts/tusertypeclasses.nim index 612556949..533bd528d 100644 --- a/tests/concepts/tusertypeclasses.nim +++ b/tests/concepts/tusertypeclasses.nim @@ -2,20 +2,22 @@ discard """ output: '''Sortable Sortable Container -true -true -false -false -false +TObj +int ''' """ import typetraits +template reject(expr) = assert(not compiles(x)) + type TObj = object x: int + JSonValue = object + val: string + Sortable = concept x, y (x < y) is bool @@ -23,7 +25,7 @@ type C.len is Ordinal for v in items(C): v.type is tuple|object - + proc foo(c: ObjectContainer) = echo "Container" @@ -36,33 +38,62 @@ 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) - intval T.y - let z = intval(T.y) + TFoo = concept o, type T, ref r, var v, ptr p, static s + o.x + y(o) is int -proc y(x: TObj): int = 10 + var str: string + var intref: ref int -proc testFoo(x: TFoo) = discard -testFoo(TObj(x: 10)) + refproc(ref T, ref int) + varproc(var T) + ptrproc(ptr T, str) -type - Matrix[Rows, Cols: static[int]; T] = concept M - M.M == Rows - M.N == Cols - M.T is T + staticproc(static[T]) + + typeproc T + T.typeproc + typeproc o.type + o.type.typeproc - MyMatrix[M, N: static[int]; T] = object - data: array[M*N, T] + o.to(type string) + o.to(type JsonValue) -var x: MyMatrix[3, 3, int] + refproc(r, intref) + varproc(v) + p.ptrproc(string) + staticproc s + typeproc(T) -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] + const TypeName = T.name + type MappedType = type(o.y) + + intval y(o) + 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 to(x: TObj, t: type string) = discard +proc to(x: TObj, t: type JSonValue) = discard + +proc testFoo(x: TFoo) = + echo x.TypeName + echo x.MappedType.name + +testFoo(TObj(x: 10)) 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 + diff --git a/tests/generics/tgenericdotrettype.nim b/tests/generics/tgenericdotrettype.nim new file mode 100644 index 000000000..3e4614752 --- /dev/null +++ b/tests/generics/tgenericdotrettype.nim @@ -0,0 +1,29 @@ +discard """ +output: '''string +int +(int, string) +''' +""" + +import typetraits + +type + Foo[T, U] = object + x: T + y: U + +proc bar[T](a: T): T.U = + echo result.type.name + +proc bas(x: auto): x.T = + echo result.type.name + +proc baz(x: Foo): (Foo.T, x.U) = + echo result.type.name + +var + f: Foo[int, string] + x = bar f + z = bas f + y = baz f + 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'" """ diff --git a/tests/testament/tester.nim b/tests/testament/tester.nim index d4a161dab..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,6 +186,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.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: r.addResult(test, expected.file, given.file, reFilesDiffer) |