diff --git a/changelog.md b/changelog.md index 4a490dfdde..31b31c5bf8 100644 --- a/changelog.md +++ b/changelog.md @@ -119,6 +119,13 @@ - In order to make ``for`` loops and iterators more flexible to use Nim now supports so called "for-loop macros". See the `manual `_ for more details. +- the `typedesc` special type has been renamed to just `type`. +- `static` and `type` are now also modifiers similar to `ref` and `ptr`. + They denote the special types `static[T]` and `type[T]`. +- Forcing compile-time evaluation with `static` now supports specifying + the desired target type (as a concrete type or as a type class) +- The `type` operator now supports checking that the supplied expression + matches an expected type constraint. ### Language changes diff --git a/compiler/ast.nim b/compiler/ast.nim index 40c1b064d2..6302c21b98 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -293,6 +293,10 @@ const # the compiler will avoid printing such names # in user messages. + sfHoisted* = sfForward + # an expression was hoised to an anonymous variable. + # the flag is applied to the var/let symbol + sfNoForward* = sfRegister # forward declarations are not required (per module) sfReorder* = sfForward @@ -454,6 +458,9 @@ type nfPreventCg # this node should be ignored by the codegen nfBlockArg # this a stmtlist appearing in a call (e.g. a do block) nfFromTemplate # a top-level node returned from a template + nfDefaultParam # an automatically inserter default parameter + nfDefaultRefsParam # a default param value references another parameter + # the flag is applied to proc default values and to calls TNodeFlags* = set[TNodeFlag] TTypeFlag* = enum # keep below 32 for efficiency reasons (now: beyond that) @@ -571,8 +578,8 @@ type TMagic* = enum # symbols that require compiler magic: mNone, mDefined, mDefinedInScope, mCompiles, mArrGet, mArrPut, mAsgn, - mLow, mHigh, mSizeOf, mTypeTrait, mIs, mOf, mAddr, mTypeOf, mRoof, mPlugin, - mEcho, mShallowCopy, mSlurp, mStaticExec, + mLow, mHigh, mSizeOf, mTypeTrait, mIs, mOf, mAddr, mType, mTypeOf, + mRoof, mPlugin, mEcho, mShallowCopy, mSlurp, mStaticExec, mStatic, mParseExprToAst, mParseStmtToAst, mExpandToAst, mQuoteAst, mUnaryLt, mInc, mDec, mOrd, mNew, mNewFinalize, mNewSeq, mNewSeqOfCap, @@ -971,7 +978,7 @@ const PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16, nfDotSetter, nfDotField, nfIsRef, nfPreventCg, nfLL, - nfFromTemplate} + nfFromTemplate, nfDefaultRefsParam} namePos* = 0 patternPos* = 1 # empty except for term rewriting macros genericParamsPos* = 2 diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim index 0afe56bb70..290ac05ee9 100644 --- a/compiler/astalgo.nim +++ b/compiler/astalgo.nim @@ -31,26 +31,39 @@ when declared(echo): proc debug*(conf: ConfigRef; n: PType) {.deprecated.} proc debug*(conf: ConfigRef; n: PNode) {.deprecated.} -template mdbg*: bool {.dirty.} = - when compiles(c.module): - c.module.fileIdx == c.config.projectMainIdx - elif compiles(c.c.module): - c.c.module.fileIdx == c.c.config.projectMainIdx - elif compiles(m.c.module): - m.c.module.fileIdx == m.c.config.projectMainIdx - elif compiles(cl.c.module): - cl.c.module.fileIdx == cl.c.config.projectMainIdx - elif compiles(p): - when compiles(p.lex): - p.lex.fileIdx == p.lex.config.projectMainIdx + template debug*(x: PSym|PType|PNode) {.deprecated.} = + when compiles(c.config): + debug(c.config, x) + elif compiles(c.graph.config): + debug(c.graph.config, x) else: - p.module.module.fileIdx == p.config.projectMainIdx - elif compiles(m.module.fileIdx): - m.module.fileIdx == m.config.projectMainIdx - elif compiles(L.fileIdx): - L.fileIdx == L.config.projectMainIdx - else: - error() + error() + + template debug*(x: auto) {.deprecated.} = + echo x + + template mdbg*: bool {.deprecated.} = + when compiles(c.graph): + c.module.fileIdx == c.graph.config.projectMainIdx + elif compiles(c.module): + c.module.fileIdx == c.config.projectMainIdx + elif compiles(c.c.module): + c.c.module.fileIdx == c.c.config.projectMainIdx + elif compiles(m.c.module): + m.c.module.fileIdx == m.c.config.projectMainIdx + elif compiles(cl.c.module): + cl.c.module.fileIdx == cl.c.config.projectMainIdx + elif compiles(p): + when compiles(p.lex): + p.lex.fileIdx == p.lex.config.projectMainIdx + else: + p.module.module.fileIdx == p.config.projectMainIdx + elif compiles(m.module.fileIdx): + m.module.fileIdx == m.config.projectMainIdx + elif compiles(L.fileIdx): + L.fileIdx == L.config.projectMainIdx + else: + error() # --------------------------- ident tables ---------------------------------- proc idTableGet*(t: TIdTable, key: PIdObj): RootRef diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index 56587a4faf..8b2d36722c 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -44,6 +44,7 @@ proc initDefines*(symbols: StringTableRef) = defineSymbol("nimcomputedgoto") defineSymbol("nimunion") defineSymbol("nimnewshared") + defineSymbol("nimNewTypedesc") defineSymbol("nimrequiresnimframe") defineSymbol("nimparsebiggestfloatmagic") defineSymbol("nimalias") diff --git a/compiler/lowerings.nim b/compiler/lowerings.nim index 24a4f186e7..1b17f620c8 100644 --- a/compiler/lowerings.nim +++ b/compiler/lowerings.nim @@ -336,6 +336,18 @@ proc typeNeedsNoDeepCopy(t: PType): bool = if t.kind in {tyVar, tyLent, tySequence}: t = t.lastSon result = not containsGarbageCollectedRef(t) +proc hoistExpr*(varSection, expr: PNode, name: PIdent, owner: PSym): PSym = + result = newSym(skLet, name, owner, varSection.info, owner.options) + result.flags.incl sfHoisted + result.typ = expr.typ + + var varDef = newNodeI(nkIdentDefs, varSection.info, 3) + varDef.sons[0] = newSymNode(result) + varDef.sons[1] = newNodeI(nkEmpty, varSection.info) + varDef.sons[2] = expr + + varSection.add varDef + proc addLocalVar(g: ModuleGraph; varSection, varInit: PNode; owner: PSym; typ: PType; v: PNode; useShallowCopy=false): PSym = result = newSym(skTemp, getIdent(g.cache, genPrefix), owner, varSection.info, diff --git a/compiler/parser.nim b/compiler/parser.nim index f575f3d7ef..c513fac680 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -50,6 +50,9 @@ type SymbolMode = enum smNormal, smAllowNil, smAfterDot + TPrimaryMode = enum + pmNormal, pmTypeDesc, pmTypeDef, pmSkipSuffix + proc parseAll*(p: var TParser): PNode proc closeParser*(p: var TParser) proc parseTopLevelStmt*(p: var TParser): PNode @@ -81,6 +84,9 @@ proc parsePragma(p: var TParser): PNode proc postExprBlocks(p: var TParser, x: PNode): PNode proc parseExprStmt(p: var TParser): PNode proc parseBlock(p: var TParser): PNode +proc primary(p: var TParser, mode: TPrimaryMode): PNode +proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode + # implementation proc getTok(p: var TParser) = @@ -332,6 +338,8 @@ proc colcom(p: var TParser, n: PNode) = eat(p, tkColon) skipComment(p, n) +const tkBuiltInMagics = {tkType, tkStatic, tkAddr} + proc parseSymbol(p: var TParser, mode = smNormal): PNode = #| symbol = '`' (KEYW|IDENT|literal|(operator|'('|')'|'['|']'|'{'|'}'|'=')+)+ '`' #| | IDENT | KEYW @@ -340,7 +348,7 @@ proc parseSymbol(p: var TParser, mode = smNormal): PNode = result = newIdentNodeP(p.tok.ident, p) getTok(p) of tokKeywordLow..tokKeywordHigh: - if p.tok.tokType == tkAddr or p.tok.tokType == tkType or mode == smAfterDot: + if p.tok.tokType in tkBuiltInMagics or mode == smAfterDot: # for backwards compatibility these 2 are always valid: result = newIdentNodeP(p.tok.ident, p) getTok(p) @@ -529,9 +537,6 @@ proc parseGStrLit(p: var TParser, a: PNode): PNode = else: result = a -type - TPrimaryMode = enum pmNormal, pmTypeDesc, pmTypeDef, pmSkipSuffix - proc complexOrSimpleStmt(p: var TParser): PNode proc simpleExpr(p: var TParser, mode = pmNormal): PNode @@ -629,7 +634,7 @@ proc identOrLiteral(p: var TParser, mode: TPrimaryMode): PNode = #| tupleConstr = '(' optInd (exprColonEqExpr comma?)* optPar ')' #| arrayConstr = '[' optInd (exprColonEqExpr comma?)* optPar ']' case p.tok.tokType - of tkSymbol, tkType, tkAddr: + of tkSymbol, tkBuiltInMagics: result = newIdentNodeP(p.tok.ident, p) getTok(p) result = parseGStrLit(p, result) @@ -745,7 +750,12 @@ proc commandParam(p: var TParser, isFirstParam: var bool): PNode = addSon(result, parseExpr(p)) isFirstParam = false -proc primarySuffix(p: var TParser, r: PNode, baseIndent: int): PNode = +const + tkTypeClasses = {tkRef, tkPtr, tkVar, tkStatic, tkType, + tkEnum, tkTuple, tkObject, tkProc} + +proc primarySuffix(p: var TParser, r: PNode, + baseIndent: int, mode: TPrimaryMode): PNode = #| primarySuffix = '(' (exprColonEqExpr comma?)* ')' doBlocks? #| | doBlocks #| | '.' optInd symbol generalizedLit? @@ -762,7 +772,14 @@ proc primarySuffix(p: var TParser, r: PNode, baseIndent: int): PNode = case p.tok.tokType of tkParLe: # progress guaranteed - somePar() + if p.tok.strongSpaceA > 0: + # inside type sections, expressions such as `ref (int, bar)` + # are parsed as a nkCommand with a single tuple argument (nkPar) + if mode == pmTypeDef: + result = newNodeP(nkCommand, p) + result.addSon r + result.addSon primary(p, pmNormal) + break result = namedParams(p, result, nkCall, tkParRi) if result.len > 1 and result.sons[1].kind == nkExprColonExpr: result.kind = nkObjConstr @@ -778,8 +795,13 @@ proc primarySuffix(p: var TParser, r: PNode, baseIndent: int): PNode = # progress guaranteed somePar() result = namedParams(p, result, nkCurlyExpr, tkCurlyRi) - of tkSymbol, tkAccent, tkIntLit..tkCharLit, tkNil, tkCast, tkAddr, tkType, - tkOpr, tkDotDot: + of tkSymbol, tkAccent, tkIntLit..tkCharLit, tkNil, tkCast, + tkOpr, tkDotDot, tkTypeClasses - {tkRef, tkPtr}: + # XXX: In type sections we allow the free application of the + # command syntax, with the exception of expressions such as + # `foo ref` or `foo ptr`. Unfortunately, these two are also + # used as infix operators for the memory regions feature and + # the current parsing rules don't play well here. if p.inPragma == 0 and (isUnary(p) or p.tok.tokType notin {tkOpr, tkDotDot}): # actually parsing {.push hints:off.} as {.push(hints:off).} is a sweet # solution, but pragmas.nim can't handle that @@ -804,9 +826,6 @@ proc primarySuffix(p: var TParser, r: PNode, baseIndent: int): PNode = else: break -proc primary(p: var TParser, mode: TPrimaryMode): PNode -proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode - proc parseOperators(p: var TParser, headNode: PNode, limit: int, mode: TPrimaryMode): PNode = result = headNode @@ -1127,9 +1146,9 @@ proc parseProcExpr(p: var TParser; isExpr: bool; kind: TNodeKind): PNode = proc isExprStart(p: TParser): bool = case p.tok.tokType of tkSymbol, tkAccent, tkOpr, tkNot, tkNil, tkCast, tkIf, - tkProc, tkFunc, tkIterator, tkBind, tkAddr, + tkProc, tkFunc, tkIterator, tkBind, tkBuiltInMagics, tkParLe, tkBracketLe, tkCurlyLe, tkIntLit..tkCharLit, tkVar, tkRef, tkPtr, - tkTuple, tkObject, tkType, tkWhen, tkCase, tkOut: + tkTuple, tkObject, tkWhen, tkCase, tkOut: result = true else: result = false @@ -1190,7 +1209,6 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode = #| | 'proc' | 'iterator' | 'distinct' | 'object' | 'enum' #| primary = typeKeyw typeDescK #| / prefixOperator* identOrLiteral primarySuffix* - #| / 'static' primary #| / 'bind' primary if isOperator(p.tok): let isSigil = isSigilLike(p.tok) @@ -1203,7 +1221,7 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode = #XXX prefix operators let baseInd = p.lex.currLineIndent addSon(result, primary(p, pmSkipSuffix)) - result = primarySuffix(p, result, baseInd) + result = primarySuffix(p, result, baseInd, mode) else: addSon(result, primary(p, pmNormal)) return @@ -1233,14 +1251,6 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode = result = parseTypeClass(p) else: parMessage(p, "the 'concept' keyword is only valid in 'type' sections") - of tkStatic: - let info = parLineInfo(p) - getTokNoInd(p) - let next = primary(p, pmNormal) - if next.kind == nkBracket and next.sonsLen == 1: - result = newNode(nkStaticTy, info, @[next.sons[0]]) - else: - result = newNode(nkStaticExpr, info, @[next]) of tkBind: result = newNodeP(nkBind, p) getTok(p) @@ -1255,7 +1265,7 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode = let baseInd = p.lex.currLineIndent result = identOrLiteral(p, mode) if mode != pmSkipSuffix: - result = primarySuffix(p, result, baseInd) + result = primarySuffix(p, result, baseInd, mode) proc parseTypeDesc(p: var TParser): PNode = #| typeDesc = simpleExpr diff --git a/compiler/renderer.nim b/compiler/renderer.nim index ba87838db3..3ce2e157de 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -387,8 +387,10 @@ proc lcomma(g: TSrcGen; n: PNode, start: int = 0, theEnd: int = - 1): int = assert(theEnd < 0) result = 0 for i in countup(start, sonsLen(n) + theEnd): - inc(result, lsub(g, n.sons[i])) - inc(result, 2) # for ``, `` + let param = n.sons[i] + if nfDefaultParam notin param.flags: + inc(result, lsub(g, param)) + inc(result, 2) # for ``, `` if result > 0: dec(result, 2) # last does not get a comma! diff --git a/compiler/sem.nim b/compiler/sem.nim index 3b16e0938b..299286545e 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -48,7 +48,10 @@ proc semQuoteAst(c: PContext, n: PNode): PNode proc finishMethod(c: PContext, s: PSym) proc evalAtCompileTime(c: PContext, n: PNode): PNode proc indexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode - +proc semStaticExpr(c: PContext, n: PNode): PNode +proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType +proc semTypeOf(c: PContext; n: PNode): PNode +proc hasUnresolvedArgs(c: PContext, n: PNode): bool proc isArrayConstr(n: PNode): bool {.inline.} = result = n.kind == nkBracket and n.typ.skipTypes(abstractInst).kind == tyArray @@ -70,6 +73,16 @@ template semIdeForTemplateOrGeneric(c: PContext; n: PNode; # echo "passing to safeSemExpr: ", renderTree(n) discard safeSemExpr(c, n) +proc fitNodePostMatch(c: PContext, formal: PType, arg: PNode): PNode = + result = arg + let x = result.skipConv + if x.kind in {nkPar, nkTupleConstr} and formal.kind != tyExpr: + changeType(c, x, formal, check=true) + else: + result = skipHiddenSubConv(result) + #result.typ = takeType(formal, arg.typ) + #echo arg.info, " picked ", result.typ.typeToString + proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode = if arg.typ.isNil: localError(c.config, arg.info, "expression has no type: " & @@ -85,13 +98,7 @@ proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode = result = copyTree(arg) result.typ = formal else: - let x = result.skipConv - if x.kind in {nkPar, nkTupleConstr} and formal.kind != tyExpr: - changeType(c, x, formal, check=true) - else: - result = skipHiddenSubConv(result) - #result.typ = takeType(formal, arg.typ) - #echo arg.info, " picked ", result.typ.typeToString + result = fitNodePostMatch(c, formal, result) proc inferWithMetatype(c: PContext, formal: PType, arg: PNode, coerceDistincts = false): PNode diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 5d3df064f4..67fe992321 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -391,7 +391,23 @@ proc inferWithMetatype(c: PContext, formal: PType, result = copyTree(arg) result.typ = formal -proc semResolvedCall(c: PContext, n: PNode, x: TCandidate): PNode = +proc updateDefaultParams(call: PNode) = + # In generic procs, the default parameter may be unique for each + # instantiation (see tlateboundgenericparams). + # After a call is resolved, we need to re-assign any default value + # that was used during sigmatch. sigmatch is responsible for marking + # the default params with `nfDefaultParam` and `instantiateProcType` + # computes correctly the default values for each instantiation. + let calleeParams = call[0].sym.typ.n + for i in countdown(call.len - 1, 1): + if nfDefaultParam notin call[i].flags: + return + let def = calleeParams[i].sym.ast + if nfDefaultRefsParam in def.flags: call.flags.incl nfDefaultRefsParam + call[i] = def + +proc semResolvedCall(c: PContext, x: TCandidate, + n: PNode, flags: TExprFlags): PNode = assert x.state == csMatch var finalCallee = x.calleeSym markUsed(c.config, n.sons[0].info, finalCallee, c.graph.usageSym) @@ -424,8 +440,9 @@ proc semResolvedCall(c: PContext, n: PNode, x: TCandidate): PNode = result = x.call instGenericConvertersSons(c, result, x) - result.sons[0] = newSymNode(finalCallee, result.sons[0].info) + result[0] = newSymNode(finalCallee, result[0].info) result.typ = finalCallee.typ.sons[0] + updateDefaultParams(result) proc canDeref(n: PNode): bool {.inline.} = result = n.len >= 2 and (let t = n[1].typ; @@ -447,7 +464,7 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode, message(c.config, n.info, hintUserRaw, "Non-matching candidates for " & renderTree(n) & "\n" & candidates) - result = semResolvedCall(c, n, r) + result = semResolvedCall(c, r, n, flags) elif implicitDeref in c.features and canDeref(n): # try to deref the first argument and then try overloading resolution again: # @@ -458,7 +475,7 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode, # n.sons[1] = n.sons[1].tryDeref var r = resolveOverloads(c, n, nOrig, filter, flags, errors, efExplain in flags) - if r.state == csMatch: result = semResolvedCall(c, n, r) + if r.state == csMatch: result = semResolvedCall(c, r, n, flags) else: # get rid of the deref again for a better error message: n.sons[1] = n.sons[1].sons[0] diff --git a/compiler/semdata.nim b/compiler/semdata.nim index c858b68392..aa0cb6e8ec 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -288,7 +288,8 @@ proc makeTypeDesc*(c: PContext, typ: PType): PType = result.addSonSkipIntLit(typ) proc makeTypeSymNode*(c: PContext, typ: PType, info: TLineInfo): PNode = - let typedesc = makeTypeDesc(c, typ) + let typedesc = newTypeS(tyTypeDesc, c) + typedesc.addSonSkipIntLit(assertNotNil(c.config, typ)) let sym = newSym(skType, c.cache.idAnon, getCurrOwner(c), info, c.config.options).linkTo(typedesc) return newSymNode(sym, info) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index d7b5667b9a..5d6eaf6529 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -191,7 +191,25 @@ proc semConv(c: PContext, n: PNode): PNode = return n result = newNodeI(nkConv, n.info) - var targetType = semTypeNode(c, n.sons[0], nil).skipTypes({tyTypeDesc}) + + var targetType = semTypeNode(c, n.sons[0], nil) + if targetType.kind == tyTypeDesc: + internalAssert c.config, targetType.len > 0 + if targetType.base.kind == tyNone: + return semTypeOf(c, n[1]) + else: + targetType = targetType.base + elif targetType.kind == tyStatic: + var evaluated = semStaticExpr(c, n[1]) + if evaluated.kind == nkType or evaluated.typ.kind == tyTypeDesc: + result = n + result.typ = c.makeTypeDesc semStaticType(c, evaluated, nil) + return + elif targetType.base.kind == tyNone: + return evaluated + else: + targetType = targetType.base + maybeLiftType(targetType, c, n[0].info) if targetType.kind in {tySink, tyLent}: @@ -298,57 +316,98 @@ proc semSizeof(c: PContext, n: PNode): PNode = n.typ = getSysType(c.graph, n.info, tyInt) result = n +proc fixupStaticType(c: PContext, n: PNode) = + # This proc can be applied to evaluated expressions to assign + # them a static type. + # + # XXX: with implicit static, this should not be necessary, + # because the output type of operations such as `semConstExpr` + # should be a static type (as well as the type of any other + # expression that can be implicitly evaluated). For now, we + # apply this measure only in code that is enlightened to work + # with static types. + if n.typ.kind != tyStatic: + n.typ = newTypeWithSons(getCurrOwner(c), tyStatic, @[n.typ]) + n.typ.n = n # XXX: cycles like the one here look dangerous. + # Consider using `n.copyTree` + proc isOpImpl(c: PContext, n: PNode, flags: TExprFlags): PNode = - internalAssert c.config, n.sonsLen == 3 and - n[1].typ != nil and n[1].typ.kind == tyTypeDesc and + internalAssert c.config, + n.sonsLen == 3 and + n[1].typ != nil and n[2].kind in {nkStrLit..nkTripleStrLit, nkType} - let t1 = n[1].typ.skipTypes({tyTypeDesc}) + var + res = false + t1 = n[1].typ + t2 = n[2].typ + + if t1.kind == tyTypeDesc and t2.kind != tyTypeDesc: + t1 = t1.base if n[2].kind in {nkStrLit..nkTripleStrLit}: case n[2].strVal.normalize of "closure": let t = skipTypes(t1, abstractRange) - result = newIntNode(nkIntLit, ord(t.kind == tyProc and - t.callConv == ccClosure and - tfIterator notin t.flags)) + res = t.kind == tyProc and + t.callConv == ccClosure and + tfIterator notin t.flags else: - result = newIntNode(nkIntLit, 0) + res = false else: - 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 = @[] m.diagnosticsEnabled = true - let match = typeRel(m, t2, t1) >= isSubtype # isNone - result = newIntNode(nkIntLit, ord(match)) + res = typeRel(m, t2, t1) >= isSubtype # isNone + result = newIntNode(nkIntLit, ord(res)) result.typ = n.typ proc semIs(c: PContext, n: PNode, flags: TExprFlags): PNode = if sonsLen(n) != 3: localError(c.config, n.info, "'is' operator takes 2 arguments") + let boolType = getSysType(c.graph, n.info, tyBool) result = n - n.typ = getSysType(c.graph, n.info, tyBool) + n.typ = boolType + var liftLhs = true n.sons[1] = semExprWithType(c, n[1], {efDetermineType, efWantIterator}) if n[2].kind notin {nkStrLit..nkTripleStrLit}: let t2 = semTypeNode(c, n[2], nil) n.sons[2] = newNodeIT(nkType, n[2].info, t2) + if t2.kind == tyStatic: + let evaluated = tryConstExpr(c, n[1]) + if evaluated != nil: + c.fixupStaticType(evaluated) + n[1] = evaluated + else: + result = newIntNode(nkIntLit, 0) + result.typ = boolType + return + elif t2.kind == tyTypeDesc and + (t2.base.kind == tyNone or tfExplicit in t2.flags): + # When the right-hand side is an explicit type, we must + # not allow regular values to be matched against the type: + liftLhs = false - let lhsType = n[1].typ + var lhsType = n[1].typ if lhsType.kind != tyTypeDesc: - n.sons[1] = makeTypeSymNode(c, lhsType, n[1].info) - elif lhsType.base.kind == tyNone: - # this is a typedesc variable, leave for evals - return + if liftLhs: + n[1] = makeTypeSymNode(c, lhsType, n[1].info) + lhsType = n[1].typ + else: + if lhsType.base.kind == tyNone: + # this is a typedesc variable, leave for evals + return + if lhsType.base.containsGenericType: + # BUGFIX: don't evaluate this too early: ``T is void`` + return - # BUGFIX: don't evaluate this too early: ``T is void`` - if not n[1].typ.base.containsGenericType: result = isOpImpl(c, n, flags) + result = isOpImpl(c, n, flags) proc semOpAux(c: PContext, n: PNode) = const flags = {efDetermineType} @@ -483,6 +542,36 @@ proc fixAbstractType(c: PContext, n: PNode) = proc isAssignable(c: PContext, n: PNode; isUnsafeAddr=false): TAssignableResult = result = parampatterns.isAssignable(c.p.owner, n, isUnsafeAddr) +proc isUnresolvedSym(s: PSym): bool = + return s.kind == skGenericParam or + tfInferrableStatic in s.typ.flags or + (s.kind == skParam and s.typ.isMetaType) or + (s.kind == skType and + s.typ.flags * {tfGenericTypeParam, tfImplicitTypeParam} != {}) + +proc hasUnresolvedArgs(c: PContext, n: PNode): bool = + # Checks whether an expression depends on generic parameters that + # don't have bound values yet. E.g. this could happen in situations + # such as: + # type Slot[T] = array[T.size, byte] + # proc foo[T](x: default(T)) + # + # Both static parameter and type parameters can be unresolved. + case n.kind + of nkSym: + return isUnresolvedSym(n.sym) + of nkIdent, nkAccQuoted: + let ident = considerQuotedIdent(c, n) + let sym = searchInScopes(c, ident) + if sym != nil: + return isUnresolvedSym(sym) + else: + return false + else: + for i in 0.. 1: resetIdTable(cl.symMap) resetIdTable(cl.localCache) - result.sons[i] = replaceTypeVarsT(cl, result.sons[i]) - propagateToOwner(result, result.sons[i]) - internalAssert c.config, originalParams[i].kind == nkSym - when true: - let oldParam = originalParams[i].sym - let param = copySym(oldParam) - param.owner = prc - param.typ = result.sons[i] - if oldParam.ast != nil: - param.ast = fitNode(c, param.typ, oldParam.ast, oldParam.ast.info) - # don't be lazy here and call replaceTypeVarsN(cl, originalParams[i])! - result.n.sons[i] = newSymNode(param) - addDecl(c, param) - else: - let param = replaceTypeVarsN(cl, originalParams[i]) - result.n.sons[i] = param - param.sym.owner = prc - addDecl(c, result.n.sons[i].sym) + # take a note of the original type. If't a free type or static parameter + # we'll need to keep it unbound for the `fitNode` operation below... + var typeToFit = result[i] + + let needsStaticSkipping = result[i].kind == tyFromExpr + result[i] = replaceTypeVarsT(cl, result[i]) + if needsStaticSkipping: + result[i] = result[i].skipTypes({tyStatic}) + + # ...otherwise, we use the instantiated type in `fitNode` + if (typeToFit.kind != tyTypeDesc or typeToFit.base.kind != tyNone) and + (typeToFit.kind != tyStatic): + typeToFit = result[i] + + internalAssert c.config, originalParams[i].kind == nkSym + let oldParam = originalParams[i].sym + let param = copySym(oldParam) + param.owner = prc + param.typ = result[i] + + # The default value is instantiated and fitted against the final + # concrete param type. We avoid calling `replaceTypeVarsN` on the + # call head symbol, because this leads to infinite recursion. + if oldParam.ast != nil: + var def = oldParam.ast.copyTree + if def.kind == nkCall: + for i in 1 ..< def.len: + def[i] = replaceTypeVarsN(cl, def[i]) + + def = semExprWithType(c, def) + if def.referencesAnotherParam(getCurrOwner(c)): + def.flags.incl nfDefaultRefsParam + + var converted = indexTypesMatch(c, typeToFit, def.typ, def) + if converted == nil: + # The default value doesn't match the final instantiated type. + # As an example of this, see: + # https://github.com/nim-lang/Nim/issues/1201 + # We are replacing the default value with an error node in case + # the user calls an explicit instantiation of the proc (this is + # the only way the default value might be inserted). + param.ast = errorNode(c, def) + else: + param.ast = fitNodePostMatch(c, typeToFit, converted) + param.typ = result[i] + + result.n[i] = newSymNode(param) + propagateToOwner(result, result[i]) + addDecl(c, param) resetIdTable(cl.symMap) resetIdTable(cl.localCache) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 3e62652a71..a0144500e9 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -37,6 +37,12 @@ const errNoGenericParamsAllowedForX = "no generic parameters allowed for $1" errInOutFlagNotExtern = "the '$1' modifier can be used only with imported types" +const + mStaticTy = {mStatic} + mTypeTy = {mType, mTypeOf} + # XXX: This should be needed only temporarily until the C + # sources are rebuilt + proc newOrPrevType(kind: TTypeKind, prev: PType, c: PContext): PType = if prev == nil: result = newTypeS(kind, c) @@ -238,7 +244,7 @@ proc semRangeAux(c: PContext, n: PNode, prev: PType): PType = localError(c.config, n.info, "enum '$1' has holes" % typeToString(rangeT[0])) for i in 0..1: - if hasGenericArguments(range[i]): + if hasUnresolvedArgs(c, range[i]): result.n.addSon makeStaticExpr(c, range[i]) result.flags.incl tfUnresolved else: @@ -295,7 +301,7 @@ proc semArrayIndex(c: PContext, n: PNode): PType = localError(c.config, info, errOrdinalTypeExpected) result = makeRangeWithStaticExpr(c, e) if c.inGenericContext > 0: result.flags.incl tfUnresolved - elif e.kind in nkCallKinds and hasGenericArguments(e): + elif e.kind in (nkCallKinds + {nkBracketExpr}) and hasUnresolvedArgs(c, e): if not isOrdinalType(e.typ): localError(c.config, n[1].info, errOrdinalTypeExpected) # This is an int returning call, depending on an @@ -363,6 +369,7 @@ proc semTypeIdent(c: PContext, n: PNode): PSym = if result != nil: markUsed(c.config, n.info, result, c.graph.usageSym) styleCheckUse(n.info, result) + if result.kind == skParam and result.typ.kind == tyTypeDesc: # This is a typedesc param. is it already bound? # it's not bound when it's used multiple times in the @@ -388,8 +395,7 @@ proc semTypeIdent(c: PContext, n: PNode): PSym = else: localError(c.config, n.info, errTypeExpected) return errorSym(c, n) - - if result.kind != skType: + if result.kind != skType and result.magic notin (mStaticTy + mTypeTy): # this implements the wanted ``var v: V, x: V`` feature ... var ov: TOverloadIter var amb = initOverloadIter(ov, c, n) @@ -856,9 +862,9 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, result = addImplicitGenericImpl(c, newTypeS(tyGenericParam, c), nil) of tyStatic: - # proc(a: expr{string}, b: expr{nkLambda}) - # overload on compile time values and AST trees - if paramType.n != nil: return # this is a concrete type + if paramType.base.kind != tyNone and paramType.n != nil: + # this is a concrete static value + return if tfUnresolved in paramType.flags: return # already lifted let base = paramType.base.maybeLift if base.isMetaType and procKind == skMacro: @@ -879,7 +885,10 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, of tyDistinct: if paramType.sonsLen == 1: # disable the bindOnce behavior for the type class - result = liftingWalk(paramType.sons[0], true) + result = liftingWalk(paramType.base, true) + + of tyAlias: + result = liftingWalk(paramType.base) of tySequence, tySet, tyArray, tyOpenArray, tyVar, tyLent, tyPtr, tyRef, tyProc: @@ -996,13 +1005,11 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, prev: PType, kind: TSymKind; isType=false): PType = # for historical reasons (code grows) this is invoked for parameter # lists too and then 'isType' is false. - var cl: IntSet checkMinSonsLen(n, 1, c.config) result = newProcType(c, n.info, prev) - if genericParams != nil and sonsLen(genericParams) == 0: - cl = initIntSet() var check = initIntSet() var counter = 0 + for i in countup(1, n.len - 1): var a = n.sons[i] if a.kind != nkIdentDefs: @@ -1012,6 +1019,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, # pass over this instantiation: if a.kind == nkSym and sfFromGeneric in a.sym.flags: continue illFormedAst(a, c.config) + checkMinSonsLen(a, 3, c.config) var typ: PType = nil @@ -1020,26 +1028,52 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, length = sonsLen(a) hasType = a.sons[length-2].kind != nkEmpty hasDefault = a.sons[length-1].kind != nkEmpty + if hasType: typ = semParamType(c, a.sons[length-2], constraint) if hasDefault: - def = semExprWithType(c, a.sons[length-1]) - # check type compatibility between def.typ and typ: + def = a[^1] + block determineType: + if genericParams != nil and genericParams.len > 0: + def = semGenericStmt(c, def) + if hasUnresolvedArgs(c, def): + def.typ = makeTypeFromExpr(c, def.copyTree) + break determineType + + def = semExprWithType(c, def, {efDetermineType}) + if def.referencesAnotherParam(getCurrOwner(c)): + def.flags.incl nfDefaultRefsParam + if typ == nil: typ = def.typ - elif def != nil: - # and def.typ != nil and def.typ.kind != tyNone: + if typ.kind == tyTypeDesc: + # consider a proc such as: + # proc takesType(T = int) + # a naive analysis may conclude that the proc type is type[int] + # which will prevent other types from matching - clearly a very + # surprising behavior. We must instead fix the expected type of + # the proc to be the unbound typedesc type: + typ = newTypeWithSons(c, tyTypeDesc, @[newTypeS(tyNone, c)]) + + else: + # if def.typ != nil and def.typ.kind != tyNone: # example code that triggers it: # proc sort[T](cmp: proc(a, b: T): int = cmp) if not containsGenericType(typ): + # check type compatibility between def.typ and typ: def = fitNode(c, typ, def, def.info) + elif typ.kind == tyStatic: + def = semConstExpr(c, def) + def = fitNode(c, typ, def, def.info) + if not hasType and not hasDefault: if isType: localError(c.config, a.info, "':' expected") if kind in {skTemplate, skMacro}: typ = newTypeS(tyExpr, c) elif skipTypes(typ, {tyGenericInst, tyAlias, tySink}).kind == tyVoid: continue + for j in countup(0, length-3): var arg = newSymG(skParam, a.sons[j], c) if not hasType and not hasDefault and kind notin {skTemplate, skMacro}: @@ -1055,7 +1089,8 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, arg.position = counter arg.constraint = constraint inc(counter) - if def != nil and def.kind != nkEmpty: arg.ast = copyTree(def) + if def != nil and def.kind != nkEmpty: + arg.ast = copyTree(def) if containsOrIncl(check, arg.name.id): localError(c.config, a.sons[j].info, "attempt to redefine: '" & arg.name.s & "'") addSon(result.n, newSymNode(arg)) @@ -1310,7 +1345,7 @@ proc semTypeClass(c: PContext, n: PNode, prev: PType): PType = incl dummyParam.flags, sfUsed addDecl(c, dummyParam) - result.n.sons[3] = semConceptBody(c, n[3]) + result.n[3] = semConceptBody(c, n[3]) closeScope(c) proc semProcTypeWithScope(c: PContext, n: PNode, @@ -1349,6 +1384,12 @@ proc symFromExpectedTypeNode(c: PContext, n: PNode): PSym = localError(c.config, n.info, errTypeExpected) result = errorSym(c, n) +proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType = + result = newOrPrevType(tyStatic, prev, c) + var base = semTypeNode(c, childNode, nil).skipTypes({tyTypeDesc, tyAlias}) + result.rawAddSon(base) + result.flags.incl tfHasStatic + proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = nil inc c.inTypeContext @@ -1456,7 +1497,11 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of mSeq: result = semContainer(c, n, tySequence, "seq", prev) of mOpt: result = semContainer(c, n, tyOpt, "opt", prev) of mVarargs: result = semVarargs(c, n, prev) - of mTypeDesc: result = makeTypeDesc(c, semTypeNode(c, n[1], nil)) + of mTypeDesc, mTypeTy: + result = makeTypeDesc(c, semTypeNode(c, n[1], nil)) + result.flags.incl tfExplicit + of mStaticTy: + result = semStaticType(c, n[1], prev) of mExpr: result = semTypeNode(c, n.sons[0], nil) if result != nil: @@ -1545,11 +1590,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of nkPtrTy: result = semAnyRef(c, n, tyPtr, prev) of nkVarTy: result = semVarType(c, n, prev) of nkDistinctTy: result = semDistinct(c, n, prev) - of nkStaticTy: - result = newOrPrevType(tyStatic, prev, c) - var base = semTypeNode(c, n.sons[0], nil).skipTypes({tyTypeDesc}) - result.rawAddSon(base) - result.flags.incl tfHasStatic + of nkStaticTy: result = semStaticType(c, n[0], prev) of nkIteratorTy: if n.sonsLen == 0: result = newTypeS(tyBuiltInTypeClass, c) @@ -1645,9 +1686,12 @@ proc processMagicType(c: PContext, m: PSym) = of mStmt: setMagicType(c.config, m, tyStmt, 0) if m.name.s == "stmt": m.typ.flags.incl tfOldSchoolExprStmt - of mTypeDesc: + of mTypeDesc, mType: setMagicType(c.config, m, tyTypeDesc, 0) rawAddSon(m.typ, newTypeS(tyNone, c)) + of mStatic: + setMagicType(c.config, m, tyStatic, 0) + rawAddSon(m.typ, newTypeS(tyNone, c)) of mVoidType: setMagicType(c.config, m, tyVoid, 0) of mArray: diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index a24972d040..22ea09af1e 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -144,17 +144,6 @@ proc isTypeParam(n: PNode): bool = (n.sym.kind == skGenericParam or (n.sym.kind == skType and sfFromGeneric in n.sym.flags)) -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: - for i in 0..`_. `#len,seq[T] `_ * ``iterator pairs[T](a: seq[T]): tuple[key: int, val: T] {.inline.}`` **=>** `#pairs.i,seq[T] `_ -* ``template newException[](exceptn: typedesc; message: string): expr`` **=>** - `#newException.t,typedesc,string - `_ +* ``template newException[](exceptn: type; message: string): expr`` **=>** + `#newException.t,type,string + `_ Index (idx) file format diff --git a/doc/manual.rst b/doc/manual.rst index 8e548afdc1..c267c706f8 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -1732,7 +1732,7 @@ But it seems all this boilerplate code needs to be repeated for the ``Euro`` currency. This can be solved with templates_. .. code-block:: nim - template additive(typ: typedesc) = + template additive(typ: type) = proc `+` *(x, y: typ): typ {.borrow.} proc `-` *(x, y: typ): typ {.borrow.} @@ -1740,13 +1740,13 @@ currency. This can be solved with templates_. proc `+` *(x: typ): typ {.borrow.} proc `-` *(x: typ): typ {.borrow.} - template multiplicative(typ, base: typedesc) = + template multiplicative(typ, base: type) = proc `*` *(x: typ, y: base): typ {.borrow.} proc `*` *(x: base, y: typ): typ {.borrow.} proc `div` *(x: typ, y: base): typ {.borrow.} proc `mod` *(x: typ, y: base): typ {.borrow.} - template comparable(typ: typedesc) = + template comparable(typ: type) = proc `<` * (x, y: typ): bool {.borrow.} proc `<=` * (x, y: typ): bool {.borrow.} proc `==` * (x, y: typ): bool {.borrow.} @@ -2396,7 +2396,7 @@ argument's resolution: rem unresolvedExpression(undeclaredIdentifier) ``untyped`` and ``varargs[untyped]`` are the only metatype that are lazy in this sense, the other -metatypes ``typed`` and ``typedesc`` are not lazy. +metatypes ``typed`` and ``type`` are not lazy. Varargs matching @@ -4274,29 +4274,6 @@ therefore very useful for type specialization within generic code: deletedKeys: seq[bool] -Type operator -------------- - -The ``type`` (in many other languages called `typeof`:idx:) operator can -be used to get the type of an expression: - -.. code-block:: nim - var x = 0 - var y: type(x) # y has type int - -If ``type`` is used to determine the result type of a proc/iterator/converter -call ``c(X)`` (where ``X`` stands for a possibly empty list of arguments), the -interpretation where ``c`` is an iterator is preferred over the -other interpretations: - -.. code-block:: nim - import strutils - - # strutils contains both a ``split`` proc and iterator, but since an - # an iterator is the preferred interpretation, `y` has the type ``string``: - var y: type("a b c".split) - - Type Classes ------------ @@ -4450,10 +4427,10 @@ the presence of callable symbols with specific signatures: 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. +In order to check for symbols accepting ``type`` params, you must prefix +the type with the explicit ``type`` modifier. The named instance of the +type, following the ``concept`` keyword is also considered to have the +explicit modifier and will be matched only as a type. .. code-block:: nim type @@ -4513,7 +4490,7 @@ The concept types can be parametric just like the regular generic types: import typetraits type - AnyMatrix*[R, C: static[int]; T] = concept m, var mvar, type M + AnyMatrix*[R, C: static int; T] = concept m, var mvar, type M M.ValueType is T M.Rows == R M.Cols == C @@ -4523,7 +4500,7 @@ The concept types can be parametric just like the regular generic types: type TransposedType = stripGenericParams(M)[C, R, T] - AnySquareMatrix*[N: static[int], T] = AnyMatrix[N, N, T] + AnySquareMatrix*[N: static int, T] = AnyMatrix[N, N, T] AnyTransform3D* = AnyMatrix[4, 4, float] @@ -4542,7 +4519,7 @@ The concept types can be parametric just like the regular generic types: ### matrix.nim type - Matrix*[M, N: static[int]; T] = object + Matrix*[M, N: static int; T] = object data: array[M*N, T] proc `[]`*(M: Matrix; m, n: int): M.T = @@ -4554,7 +4531,7 @@ The concept types can be parametric just like the regular generic types: # 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 + template ValueType*(M: type Matrix): type = M.T ------------- ### usage.nim @@ -4582,7 +4559,7 @@ operator and also when types dependent on them are being matched: .. code-block:: nim type - MatrixReducer[M, N: static[int]; T] = concept x + 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 @@ -4771,12 +4748,12 @@ object inheritance syntax involving the ``of`` keyword: # 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]) + 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]) + proc log(format: static string, varargs[distinct StringRef]) .. @@ -4940,9 +4917,8 @@ templates: | ``notin`` and ``isnot`` have the obvious meanings. The "types" of templates can be the symbols ``untyped``, -``typed`` or ``typedesc`` (stands for *type -description*). These are "meta types", they can only be used in certain -contexts. Real types can be used too; this implies that ``typed`` expressions +``typed`` or ``type``. These are "meta types", they can only be used in certain +contexts. Regular types can be used too; this implies that ``typed`` expressions are expected. @@ -5109,7 +5085,7 @@ In templates identifiers can be constructed with the backticks notation: .. code-block:: nim :test: "nim c $1" - template typedef(name: untyped, typ: typedesc) = + template typedef(name: untyped, typ: type) = type `T name`* {.inject.} = typ `P name`* {.inject.} = ref `T name` @@ -5171,7 +5147,7 @@ template cannot be accessed in the instantiation context: .. code-block:: nim :test: "nim c $1" - template newException*(exceptn: typedesc, message: string): untyped = + template newException*(exceptn: type, message: string): untyped = var e: ref exceptn # e is implicitly gensym'ed here new(e) @@ -5493,7 +5469,7 @@ As their name suggests, static parameters must be known at compile-time: .. code-block:: nim - proc precompiledRegex(pattern: static[string]): RegEx = + proc precompiledRegex(pattern: static string): RegEx = var res {.global.} = re(pattern) return res @@ -5513,9 +5489,9 @@ Static params can also appear in the signatures of generic types: .. code-block:: nim type - Matrix[M,N: static[int]; T: Number] = array[0..(M*N - 1), T] + Matrix[M,N: static int; T: Number] = array[0..(M*N - 1), T] # Note how `Number` is just a type constraint here, while - # `static[int]` requires us to supply a compile-time int value + # `static int` requires us to supply a compile-time int value AffineTransform2D[T] = Matrix[3, 3, T] AffineTransform3D[T] = Matrix[4, 4, T] @@ -5523,53 +5499,75 @@ Static params can also appear in the signatures of generic types: var m1: AffineTransform3D[float] # OK var m2: AffineTransform2D[string] # Error, `string` is not a `Number` +Please note that ``static T`` is just a syntactic convenience for the +underlying generic type ``static[T]``. The type param can be omitted +to obtain the type class of all values known at compile-time. A more +specific type class can be created by instantiating ``static`` with +another type class. -typedesc --------- +You can force the evaluation of a certain expression at compile-time by +coercing it to a corresponding ``static`` type: -`typedesc` is a special type allowing one to treat types as compile-time values -(i.e. if types are compile-time values and all values have a type, then -typedesc must be their type). +.. code-block:: nim + import math -When used as a regular proc param, typedesc acts as a type class. The proc -will be instantiated for each unique type parameter and one can refer to the -instantiation type using the param name: + echo static(fac(5)), " ", static[bool](16.isPowerOfTwo) + +The complier will report any failure to evaluate the expression or a +possible type mismatch error. + +type[T] +------- + +In many contexts, Nim allows you to treat the names of types as regular +values. These values exists only during the compilation phase, but since +all values must have a type, ``type`` is considered their special type. + +``type`` acts like a generic type. For instance, the type of the symbol +``int`` is ``type[int]``. Just like with regular generic types, when the +generic param is ommited, ``type`` denotes the type class of all types. +As a syntactic convenience, you can also use ``type`` as a modifier. +``type int`` is considered the same as ``type[int]``. + +Procs featuring ``type`` params are considered implicitly generic. +They will be instantiated for each unique combination of supplied types +and within the body of the proc, the name of each param will refer to +the bound concrete type: .. code-block:: nim - proc new(T: typedesc): ref T = + proc new(T: type): ref T = echo "allocating ", T.name new(result) var n = Node.new var tree = new(BinaryTree[int]) -When multiple typedesc params are present, they will bind freely to different -types. To force a bind-once behavior -one can use an explicit ``typedesc[T]`` generic param: +When multiple type params are present, they will bind freely to different +types. To force a bind-once behavior one can use an explicit generic param: .. code-block:: nim - proc acceptOnlyTypePairs[T, U](A, B: typedesc[T]; C, D: typedesc[U]) + proc acceptOnlyTypePairs[T, U](A, B: type[T]; C, D: type[U]) -Once bound, typedesc params can appear in the rest of the proc signature: +Once bound, type params can appear in the rest of the proc signature: .. code-block:: nim :test: "nim c $1" - template declareVariableWithType(T: typedesc, value: T) = + template declareVariableWithType(T: type, value: T) = var x: T = value declareVariableWithType int, 42 Overload resolution can be further influenced by constraining the set of -types that will match the typedesc param: +types that will match the type param: .. code-block:: nim :test: "nim c $1" - template maxval(T: typedesc[int]): int = high(int) - template maxval(T: typedesc[float]): float = Inf + template maxval(T: type int): int = high(int) + template maxval(T: type float): float = Inf var i = int.maxval var f = float.maxval @@ -5578,7 +5576,35 @@ types that will match the typedesc param: The constraint can be a concrete type or a type class. +type operator +------------- +You can obtain the type of a given expression by constructing a ``type`` +value from it (in many other languages this is known as the `typeof`:idx: +operator): + +.. code-block:: nim + var x = 0 + var y: type(x) # y has type int + +You may add a constraint to the resulting type to trigger a compile-time error +if the expression doesn't have the expected type: + +.. code-block:: nim + var x = 0 + var y: type[object](x) # Error: type mismatch: got but expected 'object' + +If ``type`` is used to determine the result type of a proc/iterator/converter +call ``c(X)`` (where ``X`` stands for a possibly empty list of arguments), the +interpretation where ``c`` is an iterator is preferred over the +other interpretations: + +.. code-block:: nim + import strutils + + # strutils contains both a ``split`` proc and iterator, but since an + # an iterator is the preferred interpretation, `y` has the type ``string``: + var y: type("a b c".split) Special Operators @@ -7458,7 +7484,7 @@ Custom pragmas are defined using templates annotated with pragma ``pragma``: .. code-block:: nim template dbTable(name: string, table_space: string = "") {.pragma.} template dbKey(name: string = "", primary_key: bool = false) {.pragma.} - template dbForeignKey(t: typedesc) {.pragma.} + template dbForeignKey(t: type) {.pragma.} template dbIgnore {.pragma.} diff --git a/doc/tut2.rst b/doc/tut2.rst index 91cb528341..39e3bd89ad 100644 --- a/doc/tut2.rst +++ b/doc/tut2.rst @@ -618,9 +618,9 @@ Turning the ``log`` proc into a template solves this problem: log("x has the value: " & $x) The parameters' types can be ordinary types or the meta types ``untyped``, -``typed``, or ``typedesc``. -``typedesc`` stands for *type description*, and ``untyped`` means symbol lookups and -type resolution is not performed before the expression is passed to the template. +``typed``, or ``type``. ``type`` suggests that only a type symbol may be given +as an argument, and ``untyped`` means symbol lookups and type resolution is not +performed before the expression is passed to the template. If the template has no explicit return type, ``void`` is used for consistency with procs and methods. diff --git a/lib/pure/collections/deques.nim b/lib/pure/collections/deques.nim index 328308a9b3..409240b377 100644 --- a/lib/pure/collections/deques.nim +++ b/lib/pure/collections/deques.nim @@ -38,7 +38,7 @@ ## Note: For inter thread communication use ## a `Channel `_ instead. -import math +import math, typetraits type Deque*[T] = object @@ -160,16 +160,18 @@ proc peekLast*[T](deq: Deque[T]): T {.inline.} = emptyCheck(deq) result = deq.data[(deq.tail - 1) and deq.mask] -template default[T](t: typedesc[T]): T = - var v: T - v +template destroy(x: untyped) = + when defined(nimNewRuntime) and not supportsCopyMem(type(x)): + `=destroy`(x) + else: + reset(x) proc popFirst*[T](deq: var Deque[T]): T {.inline, discardable.} = ## Remove and returns the first element of the `deq`. emptyCheck(deq) dec deq.count result = deq.data[deq.head] - deq.data[deq.head] = default(type(result)) + destroy(deq.data[deq.head]) deq.head = (deq.head + 1) and deq.mask proc popLast*[T](deq: var Deque[T]): T {.inline, discardable.} = @@ -178,7 +180,34 @@ proc popLast*[T](deq: var Deque[T]): T {.inline, discardable.} = dec deq.count deq.tail = (deq.tail - 1) and deq.mask result = deq.data[deq.tail] - deq.data[deq.tail] = default(type(result)) + destroy(deq.data[deq.tail]) + +proc clear*[T](deq: var Deque[T]) {.inline.} = + ## Resets the deque so that it is empty. + for el in mitems(deq): destroy(el) + deq.count = 0 + deq.tail = deq.head + +proc shrink*[T](deq: var Deque[T], fromFirst = 0, fromLast = 0) = + ## Remove `fromFirst` elements from the front of the deque and + ## `fromLast` elements from the back. If the supplied number of + ## elements exceeds the total number of elements in the deque, + ## the deque will remain empty. + ## + ## Any user defined destructors + if fromFirst + fromLast > deq.count: + clear(deq) + return + + for i in 0 ..< fromFirst: + destroy(deq.data[deq.head]) + deq.head = (deq.head + 1) and deq.mask + + for i in 0 ..< fromLast: + destroy(deq.data[deq.tail]) + deq.tail = (deq.tail - 1) and deq.mask + + dec deq.count, fromFirst + fromLast proc `$`*[T](deq: Deque[T]): string = ## Turn a deque into its string representation. @@ -215,6 +244,22 @@ when isMainModule: assert deq.find(6) >= 0 assert deq.find(789) < 0 + block: + var d = initDeque[int](1) + d.addLast 7 + d.addLast 8 + d.addLast 10 + d.addFirst 5 + d.addFirst 2 + d.addFirst 1 + d.addLast 20 + d.shrink(fromLast = 2) + doAssert($d == "[1, 2, 5, 7, 8]") + d.shrink(2, 1) + doAssert($d == "[5, 7]") + d.shrink(2, 2) + doAssert d.len == 0 + for i in -2 .. 10: if i in deq: assert deq.contains(i) and deq.find(i) >= 0 diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim index a0bba05a4f..1ab73faea3 100644 --- a/lib/pure/streams.nim +++ b/lib/pure/streams.nim @@ -135,6 +135,11 @@ proc write*(s: Stream, x: string) = else: if x.len > 0: writeData(s, cstring(x), x.len) +proc write*(s: Stream, args: varargs[string, `$`]) = + ## writes one or more strings to the the stream. No length fields or + ## terminating zeros are written. + for str in args: write(s, str) + proc writeLine*(s: Stream, args: varargs[string, `$`]) = ## writes one or more strings to the the stream `s` followed ## by a new line. No length field or terminating zero is written. @@ -266,8 +271,8 @@ proc peekStr*(s: Stream, length: int): TaintedString = proc readLine*(s: Stream, line: var TaintedString): bool = ## reads a line of text from the stream `s` into `line`. `line` must not be ## ``nil``! May throw an IO exception. - ## A line of text may be delimited by ``CR``, ``LF`` or - ## ``CRLF``. The newline character(s) are not part of the returned string. + ## A line of text may be delimited by ```LF`` or ``CRLF``. + ## The newline character(s) are not part of the returned string. ## Returns ``false`` if the end of the file has been reached, ``true`` ## otherwise. If ``false`` is returned `line` contains no new data. line.string.setLen(0) @@ -317,6 +322,13 @@ proc peekLine*(s: Stream): TaintedString = defer: setPosition(s, pos) result = readLine(s) +iterator lines*(s: Stream): TaintedString = + ## Iterates over every line in the stream. + ## The iteration is based on ``readLine``. + var line: TaintedString + while s.readLine(line): + yield line + when not defined(js): type diff --git a/lib/system.nim b/lib/system.nim index fb02fde23e..15f952feb0 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -179,10 +179,24 @@ proc unsafeAddr*[T](x: T): ptr T {.magic: "Addr", noSideEffect.} = ## Cannot be overloaded. discard -proc `type`*(x: untyped): typeDesc {.magic: "TypeOf", noSideEffect, compileTime.} = - ## Builtin 'type' operator for accessing the type of an expression. - ## Cannot be overloaded. - discard +when defined(nimNewTypedesc): + type + `static`* {.magic: "Static".}[T] + ## meta type representing all values that can be evaluated at compile-time. + ## + ## The type coercion ``static(x)`` can be used to force the compile-time + ## evaluation of the given expression ``x``. + + `type`* {.magic: "Type".}[T] + ## meta type representing the type of all type values. + ## + ## The coercion ``type(x)`` can be used to obtain the type of the given + ## expression ``x``. +else: + proc `type`*(x: untyped): typeDesc {.magic: "TypeOf", noSideEffect, compileTime.} = + ## Builtin 'type' operator for accessing the type of an expression. + ## Cannot be overloaded. + discard proc `not` *(x: bool): bool {.magic: "Not", noSideEffect.} ## Boolean not; returns true iff ``x == false``. diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim index 7671e5962d..5bf69dd94b 100644 --- a/lib/system/nimscript.nim +++ b/lib/system/nimscript.nim @@ -182,7 +182,7 @@ template checkOsError = if err.len > 0: raise newException(OSError, err) template log(msg: string, body: untyped) = - if mode == ScriptMode.Verbose or mode == ScriptMode.Whatif: + if mode in {ScriptMode.Verbose, ScriptMode.Whatif}: echo "[NimScript] ", msg if mode != ScriptMode.WhatIf: body diff --git a/tests/concepts/libs/trie.nim b/tests/concepts/libs/trie.nim new file mode 100644 index 0000000000..bdd3184928 --- /dev/null +++ b/tests/concepts/libs/trie.nim @@ -0,0 +1,26 @@ +import + hashes, tables, trie_database + +type + MemDBTable = Table[KeccakHash, string] + + MemDB* = object + tbl: MemDBTable + +proc hash*(key: KeccakHash): int = + hashes.hash(key.data) + +proc get*(db: MemDB, key: KeccakHash): string = + db.tbl[key] + +proc del*(db: var MemDB, key: KeccakHash): bool = + if db.tbl.hasKey(key): + db.tbl.del(key) + return true + else: + return false + +proc put*(db: var MemDB, key: KeccakHash, value: string): bool = + db.tbl[key] = value + return true + diff --git a/tests/concepts/libs/trie_database.nim b/tests/concepts/libs/trie_database.nim new file mode 100644 index 0000000000..a45c648421 --- /dev/null +++ b/tests/concepts/libs/trie_database.nim @@ -0,0 +1,12 @@ +type + KeccakHash* = object + data*: string + + BytesRange* = object + bytes*: string + + TrieDatabase* = concept db + put(var db, KeccakHash, string) is bool + del(var db, KeccakHash) is bool + get(db, KeccakHash) is string + diff --git a/tests/concepts/ttrieconcept.nim b/tests/concepts/ttrieconcept.nim new file mode 100644 index 0000000000..a26e6b146a --- /dev/null +++ b/tests/concepts/ttrieconcept.nim @@ -0,0 +1,7 @@ +import libs/[trie_database, trie] + +proc takeDb(d: TrieDatabase) = discard +var mdb: MemDB + +takeDb(mdb) + diff --git a/tests/generics/tlateboundgenericparams.nim b/tests/generics/tlateboundgenericparams.nim new file mode 100644 index 0000000000..9f0580fd27 --- /dev/null +++ b/tests/generics/tlateboundgenericparams.nim @@ -0,0 +1,145 @@ +discard """ + output: "1\n10\n1\n10" + nimout: ''' +bar instantiated with 1 +bar instantiated with 10 +''' +""" + +import typetraits + +type + Foo = object + +proc defaultFoo: Foo = discard +proc defaultInt: int = 1 +proc defaultTInt(T: type): int = 2 +proc defaultTFoo[T](x: typedesc[T]): Foo = discard +proc defaultTOldSchool[T](x: typedesc[T]): T = discard +proc defaultTModern(T: type): T = discard + +proc specializedDefault(T: type int): int = 10 +proc specializedDefault(T: type string): string = "default" + +converter intFromFoo(x: Foo): int = 3 + +proc consumeInt(x: int) = + discard + +const activeTests = {1..100} + +when true: + template test(n, body) = + when n in activeTests: + block: + body + + template reject(x) = + static: assert(not compiles(x)) + + test 1: + proc t[T](val: T = defaultInt()) = + consumeInt val + + t[int]() + reject t[string]() + + test 2: + proc t1[T](val: T = defaultFoo()) = + static: + assert type(val).name == "int" + assert T.name == "int" + + consumeInt val + + # here, the converter should kick in, but notice + # how `val` is still typed `int` inside the proc. + t1[int]() + + proc t2[T](val: T = defaultFoo()) = + discard + + reject t2[string]() + + test 3: + proc tInt[T](val = defaultInt()): string = + return type(val).name + + doAssert tInt[int]() == "int" + doAssert tInt[string]() == "int" + + proc tInt2[T](val = defaultTInt(T)): string = + return type(val).name + + doAssert tInt2[int]() == "int" + doAssert tInt2[string]() == "int" + + proc tDefTModern[T](val = defaultTModern(T)): string = + return type(val).name + + doAssert tDefTModern[int]() == "int" + doAssert tDefTModern[string]() == "string" + doAssert tDefTModern[Foo]() == "Foo" + + proc tDefTOld[T](val = defaultTOldSchool(T)): string = + return type(val).name + + doAssert tDefTOld[int]() == "int" + doAssert tDefTOld[string]() == "string" + doAssert tDefTOld[Foo]() == "Foo" + + test 4: + proc t[T](val: T = defaultTFoo(T)): string = + return type(val).name + + doAssert t[int]() == "int" + doAssert t[Foo]() == "Foo" + reject t[string]() + + test 5: + proc t1[T](a: T = specializedDefault(T)): T = + return a + + doAssert t1[int]() == 10 + doAssert t1[string]() == "default" + + proc t2[T](a: T, b = specializedDefault(T)): auto = + return $a & $b + + doAssert t2(5) == "510" + doAssert t2("string ") == "string default" + + proc t3[T](a: T, b = specializedDefault(type(a))): auto = + return $a & $b + + doAssert t3(100) == "10010" + doAssert t3("another ") == "another default" + + test 6: + # https://github.com/nim-lang/Nim/issues/5595 + type + Point[T] = object + x, y: T + + proc getOrigin[T](): Point[T] = Point[T](x: 0, y: 0) + + proc rotate[T](point: Point[T], radians: float, + origin = getOrigin[T]()): Point[T] = + discard + + var p = getOrigin[float]() + var rotated = p.rotate(2.1) + + test 7: + proc bar(x: static[int]) = + static: echo "bar instantiated with ", x + echo x + + proc foo(x: static[int] = 1) = + bar(x) + + foo() + foo(10) + foo(1) + foo(10) + diff --git a/tests/lexer/tmissingnl.nim b/tests/lexer/tmissingnl.nim index 33b7debf10..095d9570e5 100644 --- a/tests/lexer/tmissingnl.nim +++ b/tests/lexer/tmissingnl.nim @@ -4,7 +4,7 @@ discard """ errormsg: "invalid indentation" """ -import strutils var s: seq[int] = @[0, 1, 2, 3, 4, 5, 6] +import strutils let s: seq[int] = @[0, 1, 2, 3, 4, 5, 6] #s[1..3] = @[] diff --git a/tests/metatype/ttypedesc1.nim b/tests/metatype/ttypedesc1.nim index e9eee581f8..837c8eccca 100644 --- a/tests/metatype/ttypedesc1.nim +++ b/tests/metatype/ttypedesc1.nim @@ -5,15 +5,16 @@ type x: T y: U -proc getTypeName(t: typedesc): string = t.name +proc getTypeName1(t: typedesc): string = t.name +proc getTypeName2(t: type): string = t.name -proc foo(T: typedesc[float], a: auto): string = +proc foo(T: type float, a: auto): string = result = "float " & $(a.len > 5) proc foo(T: typedesc[TFoo], a: int): string = result = "TFoo " & $(a) -proc foo(T: typedesc[int or bool]): string = +proc foo(T: type[int or bool]): string = var a: T a = 10 result = "int or bool " & ($a) @@ -23,8 +24,8 @@ template foo(T: typedesc[seq]): string = "seq" test "types can be used as proc params": # XXX: `check` needs to know that TFoo[int, float] is a type and # cannot be assigned for a local variable for later inspection - check ((string.getTypeName == "string")) - check ((getTypeName(int) == "int")) + check ((string.getTypeName1 == "string")) + check ((getTypeName2(int) == "int")) check ((foo(TFoo[int, float], 1000) == "TFoo 1000")) @@ -37,6 +38,25 @@ test "types can be used as proc params": check ((foo(seq[int]) == "seq")) check ((foo(seq[TFoo[bool, string]]) == "seq")) -when false: - proc foo(T: typedesc[seq], s: T) = nil +template accept(x) = + static: assert(compiles(x)) + +template reject(x) = + static: assert(not compiles(x)) + +var + si: seq[int] + ss: seq[string] + +proc foo(T: typedesc[seq], s: T) = + discard + +accept: + foo seq[int], si + +reject: + foo seq[string], si + +reject: + foo seq[int], ss diff --git a/tests/metatype/ttypedesc2.nim b/tests/metatype/ttypedesc2.nim index 4b6cfe6bc7..94a7367e78 100644 --- a/tests/metatype/ttypedesc2.nim +++ b/tests/metatype/ttypedesc2.nim @@ -34,3 +34,17 @@ when true: type Point[T] = tuple[x, y: T] proc origin(T: typedesc): Point[T] = discard discard origin(int) + +# https://github.com/nim-lang/Nim/issues/7516 +import typetraits + +proc hasDefault1(T: type = int): auto = return T.name +doAssert hasDefault1(int) == "int" +doAssert hasDefault1(string) == "string" +doAssert hasDefault1() == "int" + +proc hasDefault2(T = string): auto = return T.name +doAssert hasDefault2(int) == "int" +doAssert hasDefault2(string) == "string" +doAssert hasDefault2() == "string" + diff --git a/tests/metatype/ttypedesc3.nim b/tests/metatype/ttypedesc3.nim index 9f19bd6e38..3d1cf2ec95 100644 --- a/tests/metatype/ttypedesc3.nim +++ b/tests/metatype/ttypedesc3.nim @@ -4,9 +4,9 @@ type Base = object of RootObj Child = object of Base -proc pr(T: typedesc[Base]) = echo "proc " & T.name -method me(T: typedesc[Base]) = echo "method " & T.name -iterator it(T: typedesc[Base]): auto = yield "yield " & T.name +proc pr(T: type[Base]) = echo "proc " & T.name +method me(T: type[Base]) = echo "method " & T.name +iterator it(T: type[Base]): auto = yield "yield " & T.name Base.pr Child.pr diff --git a/tests/metatype/ttypeselectors.nim b/tests/metatype/ttypeselectors.nim index 1209fe78f2..2a2455adb5 100644 --- a/tests/metatype/ttypeselectors.nim +++ b/tests/metatype/ttypeselectors.nim @@ -5,16 +5,16 @@ output: "8\n8\n4" import macros, typetraits -template selectType(x: int): typeDesc = +template selectType(x: int): type = when x < 10: int else: string -template simpleTypeTempl: typeDesc = +template simpleTypeTempl: type = string -macro typeFromMacro: typedesc = string +macro typeFromMacro: type = string # The tests below check that the result variable of the # selected type matches the literal types in the code: diff --git a/tests/misc/tparamsindefault.nim b/tests/misc/tparamsindefault.nim new file mode 100644 index 0000000000..c678dcc600 --- /dev/null +++ b/tests/misc/tparamsindefault.nim @@ -0,0 +1,114 @@ +discard """ +output: ''' +@[1, 2, 3]@[1, 2, 3] +a +a +1 +3 is an int +2 is an int +miau is a string +f1 1 1 1 +f1 2 3 3 +f1 10 20 30 +f2 100 100 100 +f2 200 300 300 +f2 300 400 400 +f3 10 10 20 +f3 10 15 25 +true true +false true +world +''' +""" + +template reject(x) = + assert(not compiles(x)) + +block: + # https://github.com/nim-lang/Nim/issues/7756 + proc foo[T](x: seq[T], y: seq[T] = x) = + echo x, y + + let a = @[1, 2, 3] + foo(a) + +block: + # https://github.com/nim-lang/Nim/issues/1201 + proc issue1201(x: char|int = 'a') = echo x + + issue1201() + issue1201('a') + issue1201(1) + + # https://github.com/nim-lang/Nim/issues/7000 + proc test(a: int|string = 2) = + when a is int: + echo a, " is an int" + elif a is string: + echo a, " is a string" + + test(3) # works + test() # works + test("miau") + +block: + # https://github.com/nim-lang/Nim/issues/3002 and similar + proc f1(a: int, b = a, c = b) = + echo "f1 ", a, " ", b, " ", c + + proc f2(a: int, b = a, c: int = b) = + echo "f2 ", a, " ", b, " ", c + + proc f3(a: int, b = a, c = a + b) = + echo "f3 ", a, " ", b, " ", c + + f1 1 + f1(2, 3) + f1 10, 20, 30 + 100.f2 + 200.f2 300 + 300.f2(400) + + 10.f3() + 10.f3(15) + + reject: + # This is a type mismatch error: + proc f4(a: int, b = a, c: float = b) = discard + + reject: + # undeclared identifier + proc f5(a: int, b = c, c = 10) = discard + + reject: + # undeclared identifier + proc f6(a: int, b = b) = discard + + reject: + # undeclared identifier + proc f7(a = a) = discard + +block: + proc f(a: var int, b: ptr int, c = addr(a)) = + echo addr(a) == b, " ", b == c + + var x = 10 + f(x, addr(x)) + f(x, nil, nil) + +block: + # https://github.com/nim-lang/Nim/issues/1046 + proc pySubstr(s: string, start: int, endd = s.len()): string = + var + revStart = start + revEnd = endd + + if start < 0: + revStart = s.len() + start + if endd < 0: + revEnd = s.len() + endd + + return s[revStart .. revEnd-1] + + echo pySubstr("Hello world", -5) + diff --git a/tests/parser/ttypeclasses.nim b/tests/parser/ttypeclasses.nim index 9f487c7a8c..46fd206864 100644 --- a/tests/parser/ttypeclasses.nim +++ b/tests/parser/ttypeclasses.nim @@ -1,17 +1,42 @@ -discard """ - action: run -""" - type R = ref V = var D = distinct P = ptr + T = type + S = static + OBJ = object + TPL = tuple + SEQ = seq +var i: int var x: ref int var y: distinct int var z: ptr int +const C = @[1, 2, 3] + +static: + assert x is ref + assert y is distinct + assert z is ptr + assert C is static + assert C[1] is static[int] + assert C[0] is static[SomeInteger] + assert C isnot static[string] + assert C is SEQ|OBJ + assert C isnot OBJ|TPL + assert int is int + assert int is T + assert int is SomeInteger + assert seq[int] is type + assert seq[int] is type[seq] + assert seq[int] isnot type[seq[float]] + assert i isnot type[int] + assert type(i) is type[int] + assert x isnot T + assert y isnot S + assert z isnot enum + assert x isnot object + assert y isnot tuple + assert z isnot seq -doAssert x is ref -doAssert y is distinct -doAssert z is ptr \ No newline at end of file diff --git a/tests/parser/ttypemodifiers.nim b/tests/parser/ttypemodifiers.nim new file mode 100644 index 0000000000..2c322b44ba --- /dev/null +++ b/tests/parser/ttypemodifiers.nim @@ -0,0 +1,527 @@ +discard """ +nimout: ''' +StmtList + TypeSection + TypeDef + Ident "BarePtr" + Empty + PtrTy + TypeDef + Ident "GenericPtr" + Empty + PtrTy + Bracket + Ident "int" + TypeDef + Ident "PrefixPtr" + Empty + PtrTy + Ident "int" + TypeDef + Ident "PtrTuple" + Empty + PtrTy + Par + Ident "int" + Ident "string" + TypeDef + Ident "BareRef" + Empty + RefTy + TypeDef + Ident "GenericRef" + Empty + RefTy + Bracket + Ident "int" + TypeDef + Ident "RefTupleCl" + Empty + RefTy + TupleTy + TypeDef + Ident "RefTupleType" + Empty + RefTy + Par + Ident "int" + Ident "string" + TypeDef + Ident "RefTupleVars" + Empty + RefTy + Par + Ident "a" + Ident "b" + TypeDef + Ident "BareStatic" + Empty + Ident "static" + TypeDef + Ident "GenericStatic" + Empty + BracketExpr + Ident "static" + Ident "int" + TypeDef + Ident "PrefixStatic" + Empty + Command + Ident "static" + Ident "int" + TypeDef + Ident "StaticTupleCl" + Empty + Command + Ident "static" + TupleClassTy + TypeDef + Ident "StaticTuple" + Empty + Command + Ident "static" + Par + Ident "int" + Ident "string" + TypeDef + Ident "BareType" + Empty + Ident "type" + TypeDef + Ident "GenericType" + Empty + BracketExpr + Ident "type" + Ident "float" + TypeDef + Ident "TypeTupleGen" + Empty + BracketExpr + Ident "type" + TupleClassTy + TypeDef + Ident "TypeTupleCl" + Empty + Command + Ident "type" + TupleClassTy + TypeDef + Ident "TypeInstance" + Empty + Command + Ident "type" + BracketExpr + Ident "Foo" + RefTy + TypeDef + Ident "bareTypeDesc" + Empty + Ident "typedesc" + TypeDef + Ident "TypeOfVar" + Empty + Call + Ident "type" + Ident "a" + TypeDef + Ident "TypeOfVarAlt" + Empty + Command + Ident "type" + Par + Ident "a" + TypeDef + Ident "TypeOfTuple1" + Empty + Call + Ident "type" + Ident "a" + TypeDef + Ident "TypeOfTuple2" + Empty + Call + Ident "type" + Ident "a" + Ident "b" + TypeDef + Ident "TypeOfTuple1A" + Empty + Command + Ident "type" + TupleConstr + Ident "a" + TypeDef + Ident "TypeOfTuple2A" + Empty + Command + Ident "type" + Par + Ident "a" + Ident "b" + TypeDef + Ident "TypeTuple" + Empty + Command + Ident "type" + Par + Ident "int" + Ident "string" + TypeDef + Ident "GenericTypedesc" + Empty + BracketExpr + Ident "typedesc" + Ident "int" + TypeDef + Ident "T" + Empty + Ident "type" + ProcDef + Ident "foo" + Empty + Empty + FormalParams + Ident "type" + IdentDefs + Ident "bareType" + Ident "type" + Empty + IdentDefs + Ident "genType" + BracketExpr + Ident "type" + Ident "int" + Empty + IdentDefs + Ident "typeInt" + Command + Ident "type" + Ident "int" + Empty + IdentDefs + Ident "typeIntAlt" + Call + Ident "type" + Ident "int" + Empty + IdentDefs + Ident "typeOfVar" + Call + Ident "type" + Ident "a" + Empty + IdentDefs + Ident "typeDotType" + DotExpr + Ident "foo" + Ident "type" + Empty + IdentDefs + Ident "typeTupleCl" + Command + Ident "type" + TupleClassTy + Empty + IdentDefs + Ident "bareStatic" + Ident "static" + Empty + IdentDefs + Ident "genStatic" + BracketExpr + Ident "static" + Ident "int" + Empty + IdentDefs + Ident "staticInt" + Command + Ident "static" + Ident "int" + Empty + IdentDefs + Ident "staticVal1" + Command + Ident "static" + IntLit 10 + Empty + IdentDefs + Ident "staticVal2" + Call + Ident "static" + StrLit "str" + Empty + IdentDefs + Ident "staticVal3" + Command + Ident "static" + StrLit "str" + Empty + IdentDefs + Ident "staticVal4" + CallStrLit + Ident "static" + RStrLit "str" + Empty + IdentDefs + Ident "staticDotVal" + DotExpr + IntLit 10 + Ident "static" + Empty + IdentDefs + Ident "bareRef" + RefTy + Empty + IdentDefs + Ident "refTuple1" + RefTy + Par + Ident "int" + Empty + IdentDefs + Ident "refTuple1A" + RefTy + TupleConstr + Ident "int" + Empty + IdentDefs + Ident "refTuple2" + RefTy + Par + Ident "int" + Ident "string" + Empty + IdentDefs + Ident "genRef" + RefTy + Bracket + Ident "int" + Empty + IdentDefs + Ident "refInt" + RefTy + Ident "int" + Empty + IdentDefs + Ident "refCall" + RefTy + Par + Ident "a" + Empty + IdentDefs + Ident "macroCall1" + Command + Ident "foo" + Ident "bar" + Empty + IdentDefs + Ident "macroCall2" + Call + Ident "foo" + Ident "bar" + Empty + IdentDefs + Ident "macroCall3" + Call + DotExpr + Ident "foo" + Ident "bar" + Ident "baz" + Empty + IdentDefs + Ident "macroCall4" + Call + BracketExpr + Ident "foo" + Ident "bar" + Ident "baz" + Empty + IdentDefs + Ident "macroCall5" + Command + Ident "foo" + Command + Ident "bar" + Ident "baz" + IntLit 10 + Empty + Empty + StmtList + Asgn + Ident "staticTen" + Command + Ident "static" + IntLit 10 + Asgn + Ident "staticA" + Call + Ident "static" + Ident "a" + Asgn + Ident "staticCall" + Command + Ident "static" + Call + Ident "foo" + IntLit 1 + Asgn + Ident "staticStrCall" + Command + Ident "static" + CallStrLit + Ident "foo" + RStrLit "x" + Asgn + Ident "staticChainCall" + Command + Ident "static" + Command + Ident "foo" + Ident "bar" + Asgn + Ident "typeTen" + Command + Ident "type" + IntLit 10 + Asgn + Ident "typeA" + Call + Ident "type" + Ident "a" + Asgn + Ident "typeCall" + Command + Ident "type" + Call + Ident "foo" + IntLit 1 + Asgn + Ident "typeStrCall" + Command + Ident "type" + CallStrLit + Ident "foo" + RStrLit "x" + Asgn + Ident "typeChainCall" + Command + Ident "type" + Command + Ident "foo" + Ident "bar" + Asgn + Ident "normalChainCall" + Command + Ident "foo" + Command + Ident "bar" + Ident "baz" + Asgn + Ident "normalTupleCall2" + Call + Ident "foo" + Ident "a" + Ident "b" + StaticStmt + StmtList + Ident "singleStaticStmt" + StaticStmt + StmtList + Ident "staticStmtList1" + Ident "staticStmtList2" +''' +""" + +import macros + +dumpTree: + type + BarePtr = ptr + GenericPtr = ptr[int] + PrefixPtr = ptr int + PtrTuple = ptr (int, string) + BareRef = ref + GenericRef = ref[int] + RefTupleCl = ref tuple + RefTupleType = ref (int, string) + RefTupleVars = ref (a, b) + BareStatic = static # Used to be Error: invalid indentation + GenericStatic = static[int] + PrefixStatic = static int + StaticTupleCl = static tuple + StaticTuple = static (int, string) + BareType = type + GenericType = type[float] + TypeTupleGen = type[tuple] + TypeTupleCl = type tuple # Used to be Error: invalid indentation + TypeInstance = type Foo[ref] + bareTypeDesc = typedesc + TypeOfVar = type(a) + TypeOfVarAlt = type (a) # Used to be Error: invalid indentation + TypeOfTuple1 = type(a,) + TypeOfTuple2 = type(a,b) + TypeOfTuple1A = type (a,) # Used to be Error: invalid indentation + TypeOfTuple2A = type (a,b) # Used to be Error: invalid indentation + TypeTuple = type (int, string) # Used to be Error: invalid indentation + GenericTypedesc = typedesc[int] + T = type + + proc foo( + bareType : type, + genType : type[int], + typeInt : type int, + typeIntAlt : type(int), + typeOfVar : type(a), + typeDotType : foo.type, + typeTupleCl : type tuple, # Used to be Error: ')' expected + bareStatic : static, # Used to be Error: expression expected, but found ',' + genStatic : static[int], + staticInt : static int, + staticVal1 : static 10, + staticVal2 : static("str"), + staticVal3 : static "str", + staticVal4 : static"str", # Used to be Error: expression expected, but found 'str' + staticDotVal : 10.static, + bareRef : ref, + refTuple1 : ref (int), + refTuple1A : ref (int,), + refTuple2 : ref (int,string), + genRef : ref[int], + refInt : ref int, + refCall : ref(a), + macroCall1 : foo bar, + macroCall2 : foo(bar), + macroCall3 : foo.bar(baz), + macroCall4 : foo[bar](baz), + macroCall5 : foo bar baz = 10 + ): type = + staticTen = static 10 + staticA = static(a) + # staticAspace = static (a) # With newTypedesc: Error: invalid indentation + # staticAtuple = static (a,) # With newTypedesc: Error: invalid indentation + # staticTuple = static (a,b) # With newTypedesc: Error: invalid indentation + # staticTypeTuple = static (int,string) # With newTypedesc: Error: invalid indentation + staticCall = static foo(1) + staticStrCall = static foo"x" + staticChainCall = static foo bar + + typeTen = type 10 + typeA = type(a) + # typeAspace = type (a) # Error: invalid indentation + # typeAtuple = type (a,) # Error: invalid indentation + # typeTuple = type (a,b) # Error: invalid indentation + # typeTypeTuple = type (int,string) # Error: invalid indentation + typeCall = type foo(1) + typeStrCall = type foo"x" + typeChainCall = type foo bar + + normalChainCall = foo bar baz + # normalTupleCall1 = foo(a,) # Error: invalid indentation + normalTupleCall2 = foo(a,b) + # normalTupleCall3 = foo (a,b) # Error: invalid indentation + + static: singleStaticStmt + static: + staticStmtList1 + staticStmtList2 + diff --git a/tests/statictypes/tstatictypes.nim b/tests/statictypes/tstatictypes.nim new file mode 100644 index 0000000000..789bd75886 --- /dev/null +++ b/tests/statictypes/tstatictypes.nim @@ -0,0 +1,109 @@ +discard """ +nimout: ''' +staticAlialProc instantiated with 358 +staticAlialProc instantiated with 368 +''' +output: ''' +16 +16 +b is 2 times a +17 +''' +""" + +import macros + +template ok(x) = assert(x) +template no(x) = assert(not x) + +template accept(x) = + static: assert(compiles(x)) + +template reject(x) = + static: assert(not compiles(x)) + +proc plus(a, b: int): int = a + b + +template isStatic(x: static): bool = true +template isStatic(x: auto): bool = false + +var v = 1 + +when true: + # test that `isStatic` works as expected + const C = 2 + + static: + ok C.isStatic + ok isStatic(plus(1, 2)) + ok plus(C, 2).isStatic + + no isStatic(v) + no plus(1, v).isStatic + +when true: + # test that proc instantiation works as expected + type + StaticTypeAlias = static[int] + + proc staticAliasProc(a: StaticTypeAlias, + b: static[int], + c: static int) = + static: + assert a.isStatic and b.isStatic and c.isStatic + assert isStatic(a + plus(b, c)) + echo "staticAlialProc instantiated with ", a, b, c + + when b mod a == 0: + echo "b is ", b div a, " times a" + + echo a + b + c + + staticAliasProc 1+2, 5, 8 + staticAliasProc 3, 2+3, 9-1 + staticAliasProc 3, 3+3, 4+4 + +when true: + # test static coercions. normal cases that should work: + accept: + var s1 = static[int] plus(1, 2) + var s2 = static(plus(1,2)) + var s3 = static plus(1,2) + var s4 = static[SomeInteger](1 + 2) + + # the sub-script operator can be used only with types: + reject: + var just_static3 = static[plus(1,2)] + + # static coercion takes into account the type: + reject: + var x = static[string](plus(1, 2)) + reject: + var x = static[string] plus(1, 2) + reject: + var x = static[SomeFloat] plus(3, 4) + + # you cannot coerce a run-time variable + reject: + var x = static(v) + +when true: + type + ArrayWrapper1[S: static int] = object + data: array[S + 1, int] + + ArrayWrapper2[S: static[int]] = object + data: array[S.plus(2), int] + + ArrayWrapper3[S: static[(int, string)]] = object + data: array[S[0], int] + + var aw1: ArrayWrapper1[5] + var aw2: ArrayWrapper2[5] + var aw3: ArrayWrapper3[(10, "str")] + + static: + assert aw1.data.high == 5 + assert aw2.data.high == 6 + assert aw3.data.high == 9 + diff --git a/tests/types/tisopr.nim b/tests/types/tisopr.nim index 2f9dbf245c..f05f443def 100644 --- a/tests/types/tisopr.nim +++ b/tests/types/tisopr.nim @@ -5,7 +5,7 @@ false false true true -no''' +yes''' """ proc IsVoid[T](): string =