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.. 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 candidates = "" + 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.sym.getProcHeader(prefer)) + add(candidates, "\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, ')') - 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})) - else: - add(candidates, err.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 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 .. 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) + newSym(kind, typeParamName, typeClass.sym, typeClass.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) + 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 them is also -executed once. +Much like generics, concepts are instantiated exactly once for each tested type +and any static code included within the body is executed only once. -**Hint**: Since concepts are still very rough at the edges there is a -command line switch ``--reportConceptFailures:on`` to make debugging -concept related type failures more easy. + +Concept diagnostics +------------------- + +By default, the compiler will report the matching errors in concepts only when +no other overload can be selected and a normal compilation error is produced. +When you need to understand why the compiler is not matching a particular +concept and, as a result, a wrong overload is selected, you can apply the +``explain`` pragma to either the concept body or a particular call-site. + +.. code-block:: nim + type + MyConcept {.explain.} = concept ... + + overloadedProc(x, y, z) {.explain.} + +This will provide Hints in the compiler output either every time the concept is +not matched or only on the particular call-site. + + +Generic concepts and type binding rules +--------------------------------------- + +The concept types can be parametric just like the regular generic types: + +.. code-block:: nim + ### matrixalgo.nim + + 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 .. 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. + +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. + +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 .. 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)