diff --git a/compiler/ast.nim b/compiler/ast.nim index ca931ab09244e..a71cd0d2def6a 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -427,16 +427,17 @@ type # instantiation and prior to this it has the potential to # be any type. - tyOptDeprecated - # 'out' parameter. Comparable to a 'var' parameter but every - # path must assign a value to it before it can be read from. + tyAliasSym + ## type of symbol alias tyVoid # now different from tyEmpty, hurray! static: - # remind us when TTypeKind stops to fit in a single 64-bit word assert TTypeKind.high.ord <= 63 + # remind us when TTypeKind stops to fit in a single 64-bit word + # consider merging some types if profiling shows a slowdown (though it's + # unlikely) const tyPureObject* = tyTuple @@ -499,7 +500,7 @@ type tfFromGeneric, # type is an instantiation of a generic; this is needed # because for instantiations of objects, structural # type equality has to be used - tfUnresolved, # marks unresolved typedesc/static params: e.g. + tfUnresolved, # marks unresolved typedesc/static/aliasSym params: e.g. # proc foo(T: typedesc, list: seq[T]): var T # proc foo(L: static[int]): array[L, int] # can be attached to ranges to indicate that the range @@ -588,7 +589,8 @@ type # file (it is loaded on demand, which may # mean: never) skPackage, # symbol is a package (used for canonicalization) - skAlias # an alias (needs to be resolved immediately) + skAlias, # an alias (needs to be resolved immediately) + skAliasGroup # nkOpenSymChoice as a symbol TSymKinds* = set[TSymKind] const @@ -680,8 +682,9 @@ type mInstantiationInfo, mGetTypeInfo, mGetTypeInfoV2, mNimvm, mIntDefine, mStrDefine, mBoolDefine, mRunnableExamples, mException, mBuiltinType, mSymOwner, mUncheckedArray, mGetImplTransf, - mSymIsInstantiationOf, mNodeId - + mSymIsInstantiationOf, mNodeId, + mAlias2, + mAliasSym, # things that we can evaluate safely at compile time, even if not asked for it: const @@ -832,6 +835,7 @@ type procInstCache*: seq[PInstantiation] gcUnsafetyReason*: PSym # for better error messages wrt gcsafe transformedBody*: PNode # cached body after transf pass + aliasTarget*: PSym # used for skTemplate of skModule, skPackage: # modules keep track of the generic symbols they use from other modules. # this is because in incremental compilation, when a module is about to @@ -848,6 +852,8 @@ type guard*: PSym bitsize*: int alignment*: int # for alignment + of skAliasGroup: + nodeAliasGroup*: PNode else: nil magic*: TMagic typ*: PType @@ -1212,7 +1218,7 @@ proc astdef*(s: PSym): PNode = proc isMetaType*(t: PType): bool = return t.kind in tyMetaTypes or - (t.kind == tyStatic and t.n == nil) or + (t.kind in {tyStatic, tyAliasSym} and t.n == nil) or tfHasMeta in t.flags proc isUnresolvedStatic*(t: PType): bool = diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 10c0722534a48..ccd709addd704 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -200,7 +200,7 @@ proc mapType(typ: PType): TJSTypeKind = of tyGenericParam, tyGenericBody, tyGenericInvocation, tyNone, tyFromExpr, tyForward, tyEmpty, tyUntyped, tyTyped, tyTypeDesc, tyBuiltInTypeClass, tyCompositeTypeClass, - tyAnd, tyOr, tyNot, tyAnything, tyVoid: + tyAnd, tyOr, tyNot, tyAnything, tyVoid, tyAliasSym: result = etyNone of tyGenericInst, tyInferred, tyAlias, tyUserTypeClass, tyUserTypeClassInst, tySink, tyOwned: @@ -210,7 +210,6 @@ proc mapType(typ: PType): TJSTypeKind = else: result = etyNone of tyProc: result = etyProc of tyCString: result = etyString - of tyOptDeprecated: doAssert false proc mapType(p: PProc; typ: PType): TJSTypeKind = result = mapType(typ) diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim index 9b6c179dbb183..03d23424f124f 100644 --- a/compiler/liftdestructors.nim +++ b/compiler/liftdestructors.nim @@ -785,13 +785,12 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) = of tyFromExpr, tyProxy, tyBuiltInTypeClass, tyUserTypeClass, tyUserTypeClassInst, tyCompositeTypeClass, tyAnd, tyOr, tyNot, tyAnything, tyGenericParam, tyGenericBody, tyNil, tyUntyped, tyTyped, - tyTypeDesc, tyGenericInvocation, tyForward, tyStatic: + tyTypeDesc, tyGenericInvocation, tyForward, tyStatic, tyAliasSym: #internalError(c.g.config, c.info, "assignment requested for type: " & typeToString(t)) discard of tyOrdinal, tyRange, tyInferred, tyGenericInst, tyAlias, tySink: fillBody(c, lastSon(t), body, x, y) - of tyOptDeprecated: doAssert false proc produceSymDistinctType(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp; info: TLineInfo; diff --git a/compiler/lookups.nim b/compiler/lookups.nim index 9225706b5bfd7..039fb63408787 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -80,16 +80,28 @@ iterator walkScopes*(scope: PScope): PScope = yield current current = current.parent -proc skipAlias*(s: PSym; n: PNode; conf: ConfigRef): PSym = - if s == nil or s.kind != skAlias: - result = s - else: - result = s.owner - if conf.cmd == cmdPretty: - prettybase.replaceDeprecated(conf, n.info, s, result) +proc skipAlias*(s: PSym; info: TLineInfo; conf: ConfigRef): PSym = + result = s + while true: + if result == nil: return result + if result.kind in {skParam, skConst} and result.typ != nil and result.typ.kind == tyAliasSym and result.typ.n != nil: + # `result.typ.n` can be nil for a proc declaration param + result = result.typ.n.sym + if result.nodeAliasGroup.kind == nkSym: + result = result.nodeAliasGroup.sym + elif result.kind == skAlias: + let old = result + result=result.owner + if conf.cmd == cmdPretty: + prettybase.replaceDeprecated(conf, info, old, result) + else: + message(conf, info, warnDeprecated, "use " & result.name.s & " instead; " & + old.name.s & " is deprecated") else: - message(conf, n.info, warnDeprecated, "use " & result.name.s & " instead; " & - s.name.s & " is deprecated") + return result + +proc skipAlias*(s: PSym; n: PNode; conf: ConfigRef): PSym = + skipAlias(s, n.info, conf) proc isShadowScope*(s: PScope): bool {.inline.} = s.parent != nil and s.parent.depthLevel == s.depthLevel @@ -153,6 +165,7 @@ type symChoiceIndex*: int scope*: PScope inSymChoice: IntSet + n2*: PNode proc getSymRepr*(conf: ConfigRef; s: PSym, getDeclarationPath = true): string = case s.kind @@ -379,6 +392,13 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym = if result != nil and result.kind == skStub: loadStub(result) proc initOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym = + defer: + if result != nil and result.kind == skAliasGroup: + result = initOverloadIter(o, c, result.nodeAliasGroup) + o.n2 = n + if n.typ != nil and n.typ.kind == tyAliasSym: + return initOverloadIter(o, c, n.typ.n.sym.nodeAliasGroup) + case n.kind of nkIdent, nkAccQuoted: var ident = considerQuotedIdent(c, n) @@ -436,6 +456,7 @@ proc lastOverloadScope*(o: TOverloadIter): int = else: result = -1 proc nextOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym = + let n = o.n2 # consider removing `n` from params case o.mode of oimDone: result = nil diff --git a/compiler/options.nim b/compiler/options.nim index 872ab95826cdc..56001c565e7b3 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -166,7 +166,8 @@ type ## Note: this feature can't be localized with {.push.} vmopsDanger, strictFuncs, - views + views, + featureAlias = "alias", LegacyFeature* = enum allowSemcheckedAstModification, diff --git a/compiler/sem.nim b/compiler/sem.nim index f90d9e1f9c3c4..f13aafda4215b 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -28,7 +28,6 @@ when not defined(leanCompiler): import spawn # implementation - proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode proc semExprWithType(c: PContext, n: PNode, flags: TExprFlags = {}): PNode proc semExprNoType(c: PContext, n: PNode): PNode @@ -450,6 +449,15 @@ const errMissingGenericParamsForTemplate = "'$1' has unspecified generic parameters" errFloatToString = "cannot convert '$1' to '$2'" +proc isMacroRealGeneric(s: PSym): bool = + if s.kind != skMacro: return false + if s.ast == nil: return false + let n = s.ast + if n[genericParamsPos].kind == nkEmpty: return false + for ai in n[genericParamsPos]: + if ai.typ.kind == tyAliasSym: + return true + proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, flags: TExprFlags = {}): PNode = pushInfoContext(c.config, nOrig.info, sym.detailedInfo) @@ -468,7 +476,47 @@ proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, #if c.evalContext == nil: # c.evalContext = c.createEvalContext(emStatic) - result = evalMacroCall(c.module, c.idgen, c.graph, c.templInstCounter, n, nOrig, sym) + template evalMacroCallAux: untyped = + evalMacroCall(c.module, c.idgen, c.graph, c.templInstCounter, n, nOrig, sym) + + proc evalAux(): PNode = # MOVE + # c.p.wasForwarded = proto != nil + # TODO: cleanup afterwards the changes to avoid affecting original macro + var oldPrc = sym + pushProcCon(c, oldPrc) + pushOwner(c, oldPrc) + pushInfoContext(c.config, oldPrc.info) + openScope(c) + #[ + var n = oldPrc.ast + n.sons[bodyPos] = copyTree(s.getBody) + instantiateBody(c, n, oldPrc.typ.n, oldPrc, s) + ]# + let procParams = sym.typ.n + for i in 1 ..< procParams.len: + let pi = procParams[i] + # SEE: instantiateBody + if pi.kind == nkSym and pi.sym.typ != nil and pi.sym.typ.kind == tyAliasSym: + pi.sym.typ = n.sons[i].typ # TODO: undo that after; or operate on different copy + doAssert sym.kind == skMacro + addParamOrResult(c, pi.sym, sym.kind) + # paramsTypeCheck(c, s.typ) # TODO? + maybeAddResult(c, sym, n) # CHECKME: n or sym.ast? + sym.ast[bodyPos] = hloBody(c, semProcBody(c, sym.ast[bodyPos])) + + result = evalMacroCallAux() + # skipping trackProc + + closeScope(c) + popInfoContext(c.config) + popOwner(c) + popProcCon(c) + + if isMacroRealGeneric(sym): + result = evalAux() + else: + result = evalMacroCallAux() + if efNoSemCheck notin flags: result = semAfterMacroCall(c, n, result, sym, flags) if c.config.macrosToExpand.hasKey(sym.name.s): diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 0446d28c2fb96..6bedb98c113c5 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -542,7 +542,10 @@ proc semResolvedCall(c: PContext, x: TCandidate, of skType: x.call.add newSymNode(s, n.info) else: - internalAssert c.config, false + if s.kind == skParam and s.typ != nil and s.typ.kind == tyAliasSym: # IMPROVE ; TODO: only for `aliassym`, not `typed` ? + x.call.add s.ast + else: + internalAssert c.config, false result = x.call instGenericConvertersSons(c, result, x) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index b2f0207e7caa2..182e182a84f76 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -1322,6 +1322,31 @@ proc tryReadingTypeField(c: PContext, n: PNode, i: PIdent, ty: PType): PNode = else: result = tryReadingGenericParam(c, n, i, ty) +proc resolveAliasSym(n: PNode, forceResolve = false): PNode = + #[ + `fun(arg)` where return type is tyAliasSym + if it returns a template `bar()` where bar is an iterator, it'd have no type if expanded + ]# + result = n + if result!=nil and result.typ != nil and result.typ.kind == tyAliasSym: + case result.kind + of {nkStmtListExpr, nkBlockExpr}: + # allows defining lambdaIter, lambdaIt, a~>a*2 etc in std/lambdas + let typ = result.typ + result = newSymNode(typ.n.sym) + result.info = typ.n.info + result.typ = typ + of nkSym: + if result.sym.kind == skAliasGroup: + if forceResolve: result = result.sym.nodeAliasGroup + elif result.sym.kind == skResult: + discard + else: # the alias is resolved + doAssert result.typ.n != nil + # nil would mean a aliasSym param was not instantiated; for macros, this + # requires macro instantiation + result = result.typ.n.sym.nodeAliasGroup + proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = ## returns nil if it's not a built-in field access checkSonsLen(n, 2, c.config) @@ -1412,6 +1437,9 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = if result == nil: let t = n[0].typ.skipTypes(tyDotOpTransparent) result = tryReadingGenericParam(c, n, i, t) + # xxx simplify this + if result!=nil and result.kind == nkSym and result.sym.kind == skType and result.sym.typ.base.kind == tyAliasSym: + result = resolveAliasSym(result.sym.typ.base.n, forceResolve = true) proc dotTransformation(c: PContext, n: PNode): PNode = if isSymChoice(n[1]): @@ -2181,6 +2209,60 @@ proc semSizeof(c: PContext, n: PNode): PNode = n.typ = getSysType(c.graph, n.info, tyInt) result = foldSizeOf(c.config, n, n) +proc semAlias2(c: PContext, n: PNode): PNode = + if featureAlias notin c.features: + localError(c.config, n.info, "requires '--experimental:$1'" % [$featureAlias]) + return errorNode(c, n) + var nodeOrigin = n[1] + if nodeOrigin.kind == nkOpenSymChoice: + # might originate from `semGenericStmtSymbol` which generates symChoice + # this can with default params pointing to an overload, eg: + # proc fun(a: aliassym, b: aliassym = alias2(fun1)) + nodeOrigin = n[0] + + if nodeOrigin.kind in {nkSym} and nodeOrigin.sym.kind == skUnknown: + doAssert false + + if nodeOrigin.kind notin {nkIdent, nkAccQuoted, nkSym}: + nodeOrigin = semExprWithType(c, nodeOrigin) + + if nodeOrigin.kind == nkBracketExpr: + # see BUG D20190812T234102 + nodeOrigin = nodeOrigin.sons[0].sons[nodeOrigin.sons[1].intVal] + doAssert nodeOrigin.kind != nkBracketExpr + + if nodeOrigin.typ != nil and nodeOrigin.typ.kind == tyAliasSym: + doAssert nodeOrigin.typ.n != nil + let sym2 = nodeOrigin.typ.n.sym + doAssert sym2.kind == skAliasGroup + return nodeOrigin.typ.n + + if nodeOrigin.kind notin {nkIdent, nkAccQuoted, nkSym}: + doAssert false, $nodeOrigin.kind + return nil + + let sym = qualifiedLookUp(c, nodeOrigin, {checkUndeclared, checkModule}) + if sym == nil: + globalError(c.config, n.info, errUser, "undeclared symbol: " & renderTree(nodeOrigin)) + return nil + + let sc = symChoice(c, nodeOrigin, sym, scClosed) + let sym2 = newSym(skAliasGroup, sym.name, nextId c.idgen, owner = c.getCurrOwner, info = n.info) + sym2.nodeAliasGroup = sc + + result = newSymNode(sym2) + result.info = n.info + result.typ = newTypeS(tyAliasSym, c) + result.typ.n = result # TODO: maybe `result.typ.n = sc` directly, and get rid of `skAliasGroup`? EDIT: not sure it's feasible + #[ + TODO: + should we pass nodeAliasGroup in the type or in the value? + matters, eg: + proc fun(a: sym) = + static: echo "some instantiation" + a() + ]# + proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = # this is a hotspot in the compiler! result = n @@ -2294,6 +2376,7 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = of mSizeOf: markUsed(c, n.info, s) result = semSizeof(c, setMs(n, s)) + of mAlias2: result = semAlias2(c, n) else: result = semDirectOp(c, n, flags) @@ -2627,6 +2710,9 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = when defined(nimCompilerStackraceHints): setFrameMsg c.config$n.info & " " & $n.kind result = n + defer: + result = resolveAliasSym(result) + if c.config.cmd == cmdIdeTools: suggestExpr(c, n) if nfSem in n.flags: return case n.kind @@ -2654,7 +2740,9 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = of nkSym: # because of the changed symbol binding, this does not mean that we # don't have to check the symbol for semantics here again! - result = semSym(c, n, n.sym, flags) + if n.sym.kind != skAliasGroup: # fold inside semSym? + result = semSym(c, n, n.sym, flags) + else: discard of nkEmpty, nkNone, nkCommentStmt, nkType: discard of nkNilLit: diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index 5f55772e97144..92ec33f44bea8 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -36,7 +36,8 @@ type withinBind, withinTypeDesc, withinMixin, - withinConcept + withinConcept, + withinAliasSym, TSemGenericFlags = set[TSemGenericFlag] @@ -55,7 +56,7 @@ template macroToExpand(s): untyped = template macroToExpandSym(s): untyped = sfCustomPragma notin s.flags and s.kind in {skMacro, skTemplate} and - (s.typ.len == 1) and not fromDotExpr + (s.typ.len == 1) and not fromDotExpr and withinAliasSym notin flags template isMixedIn(sym): bool = let s = sym @@ -286,7 +287,8 @@ proc semGenericStmt(c: PContext, n: PNode, # Consider 'when declared(globalsSlot): ThreadVarSetValue(globalsSlot, ...)' # in threads.nim: the subtle preprocessing here binds 'globalsSlot' which # is not exported and yet the generic 'threadProcWrapper' works correctly. - let flags = if mixinContext: flags+{withinMixin} else: flags + var flags = if mixinContext: flags+{withinMixin} else: flags + if s != nil and s.magic == mAlias2: flags.incl withinAliasSym for i in first..`*(lhs, rhs: untyped): untyped = + ## returns a lambda mapping `lhs` to `rhs`. This is side effect safe: + ## arguments will be evaluated just once. `lhs` is either 1 identifier or a + ## tuple of 0 or more identifiers. + runnableExamples "--experimental:alias": + proc callFun[T](a: aliassym, b: T): auto = a(b) + let b = 2 + doAssert callFun(x~>x*b, 3) == 3*b + var count = 0 + template identity(a): untyped = + count.inc + a + var a = "foo" # distractor: does not confuse lambda expression. + const fn = (a,b)~>a*b + doAssert fn(identity(2), 3) == 2*3 + doAssert count == 1 # side effect safe + # this works outside `runnableExamples` (pending #13491) + # const doNothing = () ~> (discard) + # doNothing() + + # xxx future work could allow param constraints, eg: `(a, b: int, c) ~> a*b+c` + var rhs = rhs + let name = genSym(nskTemplate, "lambdaArrow") + let formatParams2 = nnkFormalParams.newTree() + formatParams2.add ident("untyped") + var body2 = newStmtList() + + template addArg(argInject) = + # this doesn't work since new gensym, see #12020 + # let arg = genSym(nskParam, argInject.strVal) + # so using this workaround instead: + var count {.threadvar.}: int + count.inc + let arg = newIdentNode(argInject.strVal & "_fakegensym_" & $count) + + formatParams2.add newTree(nnkIdentDefs, arg, ident("untyped"), newEmptyNode()) + # CHECKME: let or var?, eg var could be needed? + var argInject2 = argInject + if argInject2.kind == nnkSym: + # needed, see `testArrowWrongSym`, `testArrowWrongSym2` + rhs = replaceSym(rhs, argInject) + argInject2 = ident(argInject2.strVal) + body2.add newLetStmt(argInject2, arg) + + let kind = lhs.kind + case kind + of nnkPar: # (a, b) ~> expr + for i in 0.. expr + addArg(lhs) + else: + # TODO: (a,b,) tuple? + # see D20181129T193310 + error("expected " & ${nnkPar,nnkIdent,nnkSym} & " got `" & $kind & "`") + + body2.add rhs + body2 = quote do: # TODO: option whether to use a block? + block: `body2` + + result = newStmtList() + result.add nnkTemplateDef.newTree( + name, + newEmptyNode(), + newEmptyNode(), + formatParams2, + newEmptyNode(), + newEmptyNode(), + body2 + ) + result.add newCall(bindSym"alias2", name) + +template lambdaStatic*(a: untyped): untyped = + ## returns a lambda for a const expression, which can be passed to a routine. + ## This works more reliably than `static[T]`. + runnableExamples "--experimental:alias": + type Foo[T] = object + a: T + proc fn[T1, T2](a: T1, b: T2) = # would not work with `b: static[T2]` + const b2 = b.a + fn("foo", lambdaStatic Foo[int](a: 1)) + + block: + const a2 = a + template lambdaStaticImpl(): untyped = a2 + alias2(lambdaStaticImpl) + +template lambdaType*(t: typedesc): untyped = + ## returns a lambda for a type expression, which can be passed to a routine, + ## similarly to `typedesc[T]` + runnableExamples "--experimental:alias": + proc fn(t1, t2: aliassym): string = + doAssert t1 is float32 + result = $(t1.default, $t1, $t2) + doAssert fn(lambdaType float32, lambdaType type(1u8+2u8)) == """(0.0, "float32", "uint8")""" + type T = typeof(block: (var a: t; a)) + # type T = t + # this would create a visible abstraction, eg `$` would give "T`gensym37245237" + alias2(T) diff --git a/lib/system/hti.nim b/lib/system/hti.nim index 3dbcd7615c888..5627fb3bd5b6e 100644 --- a/lib/system/hti.nim +++ b/lib/system/hti.nim @@ -69,8 +69,8 @@ type tyAnythingHidden, tyStaticHidden, tyFromExprHidden, - tyOptDeprecated, - tyVoidHidden + tyAliasSym, + tyVoidHidden, TNimNodeKind = enum nkNone, nkSlot, nkList, nkCase TNimNode {.compilerproc.} = object diff --git a/tests/magics/mlambda.nim b/tests/magics/mlambda.nim new file mode 100644 index 0000000000000..0ca381aad1e84 --- /dev/null +++ b/tests/magics/mlambda.nim @@ -0,0 +1,32 @@ +import std/lambdas + +{.push experimental: "alias".} + +proc mbar*(a0: int, funx: aliassym): auto = + ("mbar", a0, funx(a0)) + +iterator iota3(): auto = + for i in 0..<3: yield i + +const iota3Bis* = alias2 iota3 + +{.pop.} + +import std/macros + +macro elementType*(a: untyped): untyped = + ## return element type of `a`, which can be any iterable (value or iterator + ## expresssion) + runnableExamples: + iterator myiter(n: int): auto = + for i in 0..a*10, a~>a*2, 7) + doAssert a.Fun1(3) == 3*10 + doAssert a.Fun2(3) == 3*2 + +proc testAliasFields() = + # this could be improved, by supporting: `type B = object: fun1: aliassym fun2: aliassym` + type A[T1, T2, T3] = object + fun1: T1 + myconst: T2 + proc initA[T1, T2, T3](a1: T1, a2: T2, a3: T3): A[T1, T2, T3] = + typeof(result)(fun1: alias2 a1, myconst: alias2 a2) + const z = 123 + const a = initA(a~>a*10, alias2 z, (a,b)~>a*b) + static: doAssert a.myconst == 123 + doAssert a.fun1(3) == 3*10 + doAssert a.T3(3,4) == 3*4 + +proc testStatic() = + # aliassym can replace `static[T]` + proc fn(a, b: aliassym, c: int): string = + const a2 = a # sanity check to make sure it's static + const b2 = b + doAssert a is int + doAssert b is seq[string] + $(a, a2, b, b2, $type(a), $type(b), c) + doAssert fn(lambdaStatic 12, lambdaStatic @["foo", "bar"], 13) == """(12, 12, @["foo", "bar"], @["foo", "bar"], "int", "seq[string]", 13)""" + +proc testTypedesc() = + # aliassym can replace `typedesc[T]` + proc fn(t1, t2, t3: aliassym): string = + var a1: t1 + doAssert a1 is float32 + doAssert t1 is float32 + doAssert float32 is t1 + var a2: t2 + doAssert t2 is uint8 + result = $($t1, t1.default, $t2, $t3) + type Foo[T] = object + doAssert fn(lambdaType float32, lambdaType type(1u8+2u8), lambdaType Foo[int]) == """("float32", 0.0, "uint8", "Foo[system.int]")""" + +proc testLambdaIt() = + const fun = lambdaIt (it, it) + doAssert fun(3) == (3, 3) + doAssert fun("foo") == ("foo", "foo") + + icounter = 0 + doAssert fun(identity("foo")) == ("foo", "foo") + doAssert icounter == 1 # side effect safe + block: + template fun2(it): untyped = (it, it) # not side effect safe + icounter = 0 + doAssert fun2(identity("foo")) == ("foo", "foo") + doAssert icounter == 2 # side effect executed each time argument is used + +proc testArrow() = + block: # 1 arg + const fun = a ~> (a, a) + doAssert fun(3) == (3, 3) + doAssert fun("foo") == ("foo", "foo") + icounter = 0 + doAssert fun(identity("foo")) == ("foo", "foo") + doAssert icounter == 1 # side effect safe + + block: # 2 args + const fun = (a, b) ~> a*b + doAssert fun(3, 4) == 3*4 + + block: # 0 args + const fun = () ~> 14 + doAssert fun() == 14 + + block: # example with local capture + var z = 0 + const fun = () ~> (z.inc; z*10) + doAssert fun() == 10 + doAssert fun() == 20 + doAssert z == 2 + + block: # void return + var z = 0 + const fun = () ~> z.inc + for i in 0..<3: fun() + doAssert z == 3 + + block: # passing a lambda to a proc + proc mapSum[T, Fun](a: T, fun: Fun): auto = + result = default elementType(a) + for ai in a: result += fun(ai) + doAssert mapSum(@[1,2,3], x~>x*10) == 10 + 20 + 30 + doAssert mapSum(@[1,2,3], lambdaIt it*10) == 10 + 20 + 30 + + var it = 132 # not confused by this + doAssert mapSum(@[1,2,3], lambdaIt it*10) == 10 + 20 + 30 + # doAssert mapSum(@[1,2,3], it*10) == 10 + 20 + 30 + # this would work (and should not) + # it's one of the reasons why `~>` is often preferable + # (that and fact that ~> can take any number of input arguments) + + block: # aliasing things like echo, assert + const echo2 = alias2 echo + doAssert compiles echo2() + doAssert compiles echo2(1, 2) + const myAssert = alias2 assert # BUG: `const myAssert = alias2 doAssert` gives ambiguity error (with const doAssert = doAssertImpl); maybe need to propagate scope more carefully? + doAssertRaises(Exception): myAssert(1+1 == 3) + const fun = () ~> (1,2) + doAssert fun() == (1,2) + macro bar(a = 7, b: untyped): untyped = a + const bar2 = alias2 bar + doAssert bar(a = 2, b = nonexistant) == 2 + doAssert bar(b = nonexistant) == 7 + +proc testArrowWrongSym() = + iterator myIter[T2](fun: T2): auto = + yield fun(3) + template x(a: int): untyped = discard + # `x` is a distractor symbol that turns `x` into a symbol inside `bar` + template bar(): untyped = myIter(x~>x*10) + proc first[T](a: T): auto = + for ai in a(): return ai + doAssert first(alias2 bar) == 3*10 + +var countCT {.compileTime.} = 0 + +proc testProc() = + ## pass alias to proc + block: + proc bar[T](a: T, fun: aliassym): auto = fun(a) # passing to a generic + doAssert bar(10, alias2 tfun) == tfun(10) + doAssert bar(10, alias2 funo) == funo(10) # passing overload works + doAssert bar("foo", alias2 funo) == funo("foo") + doAssert bar("foo", alias2 fung) == fung("foo") # passing generic works + + block: + proc bar[T, Fun](a: T, fun: Fun): auto = fun(a) # passing aliassym as a generic T + doAssert bar(10, alias2 tfun) == tfun(10) + doAssert bar(10, alias2 funo) == funo(10) + doAssert bar("foo", alias2 funo) == funo("foo") + + block: + proc bar(a: int, fun: aliassym): auto = fun(a) # passing to a regular proc (implicitly generic) + doAssert bar(10, alias2 tfun) == tfun(10) + + block: + # multiple alias: unlike `seq`, aliassym is not bind-once + proc bar(a: int, fun1, fun2: aliassym): auto = (fun1(a), fun2(a)) + template tfun2(a): untyped = a*3 + doAssert bar(10, alias2 tfun, alias2 tfun2) == (tfun(10), tfun2(10)) + + block: + # can use at CT + proc bar(a: int, fun: aliassym): auto = + const b = fun(18) + (a,b) + doAssert bar(10, alias2 tfun) == (10, tfun(18)) + + block: + proc bar(a: int, fun: aliassym): auto = + const b = fun(18) + (a,b) + doAssert bar(10, alias2 tfun) == (10, tfun(18)) + + block: # optional params + proc bar(a: int, fun: aliassym = alias2 tfun): auto = fun(a) + doAssert bar(10, alias2 tfun) == tfun(10) + doAssert bar(10) == tfun(10) + proc bar2(a: int, fun1: aliassym, fun2: aliassym = alias2 tfun): auto = (fun1(a), fun2(a)) + doAssert bar2(10, alias2 tfun, alias2 tfun) == (tfun(10), tfun(10)) + + block: # optional params: void default works + proc bar(a: int, fun: aliassym = alias2 void): auto = + when fun is void: "def" + else: fun(a) + doAssert bar(10, alias2 tfun) == tfun(10) + doAssert bar(10) == "def" + + block: + # check correctness of `sameTypeAux` to make sure unique instantiations + # are based on whether aliased symbols are identical + proc bar(fun: aliassym) = + static: countCT.inc + proc bar2(fun: aliassym) = + static: countCT+=10 + bar(alias2 tfun) + bar(alias2 tfun) # should not re-instantiate + bar(alias2 tfunIdentical) # should re-instantiate despite tfunIdentical being structurally identical + bar2(alias2 funo) + bar2(alias2 funo) # should re-instantiate (despite being overloaded) + const countCT2 = countCT + doAssert countCT2 == 1 + 1 + 10 + + block: + proc bar[T](a: T): auto = + # make sure works inside generic and that `tfun2` doesn't get expanded too early + const y1 = alias2 tfun2 + return y1() + doAssert bar(12) == tfun2() + +import std/macros + +proc testMacro() = + ## pass alias to macro + block: + macro bari(a: int, fun: typed): untyped = + result = quote do: + (`a`, `fun`(`a`)) + doAssert bari(11, alias2 tfun) == (11, tfun(11)) + + block: + macro bari(a: static int, fun: aliassym): untyped = + newLit fun(a) + doAssert bari(11, alias2 tfun) == 11*2 + + block: + macro bari(a: bool, fun: aliassym): untyped = + let b = newLit fun(3) + let c = repr(a) + result = quote do: + (`a`, `b`, `c`) + doAssert bari(1+1 == 2, alias2 tfun) == (true, 6, "true") + + block: # pass macro to macro + macro bar1(a: int): untyped = newLit repr(a) + macro bar2(a: static int, b: int, fun: aliassym): untyped = + let s = newLit fun(a * 3) + result = quote do: (`s`, `b`) + doAssert bar2(12, 3, alias2 bar1) == ("a * 3", 3) + +proc testTemplate() = + ## pass alias to template + template bar1(fun: aliassym): untyped = + ("testTemplate.1", fun(1), fun(2), fun()) + doAssert bar1(alias2 tfun) == ("testTemplate.1", 2, 4, 2) + + template bar2[T](fun: T): untyped = + ("testTemplate.2", fun(1), fun(2)) + doAssert bar2(a~>a*10) == ("testTemplate.2", 10, 20) + +proc test5702() = # fix https://github.com/nim-lang/Nim/issues/5702 + iterator zip[T1, T2](a: openarray[T1], b: openarray[T2]): (T1,T2) {.inline.} = + let len = min(a.len, b.len) + for i in 0..a mod 2 == 0)) == @[2, 4] + doAssert toSeq(lambdaIter filter(lambdaIter iota(6), a~>a mod 2 == 0)) == @[0, 2, 4] + doAssert toSeq(lambdaIter map(lambdaIter iota(3), a~>a*10)) == @[0, 10, 20] + + ## iterator composition; note that it's all lazy until toSeq is called + const a0 = lambdaIter map(lambdaIter iota(10), a ~> a * 2) + doAssert toSeq(lambdaIter a0()) == @[0, 2, 4, 6, 8, 10, 12, 14, 16, 18] + const a1 = lambdaIter filter(lambdaIter a0(), a ~> a < 6) + doAssert toSeq(lambdaIter a1()) == @[0, 2, 4] + const a3 = lambdaIter join(lambdaIter iota(3), lambdaIter iota(2)) + doAssert toSeq(lambdaIter a3) == @[0, 1, 2, 0, 1] + +proc testIssue4516*() = # solution for https://github.com/nim-lang/Nim/issues/4516 + # iterator test2(it: iterator(): int {.inline.}): int = # would not work without aliassym + iterator test2(it: aliassym): int = + for i in it(): + yield i*2 + + iterator test1(): int = + yield 10 + yield 20 + yield 30 + + var s: seq[int] + for i in test2(lambdaIter test1()): + s.add i + doAssert s == @[20, 40, 60] + +proc testIterator2*() = + iterator iterAux(a: aliassym): auto = + for x in a: yield x + + proc iterAux3(a: aliassym): aliassym = + iterator bar(): int {.inline.} = + for x in a: yield x + lambdaIter bar() + + proc iterAux4(a: aliassym): aliassym = + iterator bar(): int {.inline.} = + for x in a: yield x*10 + alias2 bar + + proc iterAux5[T1, T2](a: T1, b: T2): aliassym = + iterator bar(): auto {.inline.} = + for x in a: yield x + for x in b: yield x + lambdaIter bar() + + iterator iterAux6(a, b: aliassym): auto {.inline.} =# doesn't work + for x in a: yield x + for x in b: yield x + + iterator myiter[T](a: T): auto = + for x in a: yield x + + doAssert elementType(iota3()) is int + const myiter2a = alias2 iota3 + block: + var ret: seq[int] + for ai in myiter2a(): ret.add ai + doAssert ret == @[0,1,2] + + block: + const myiter2b = lambdaIter iota3() + doAssert type(myiter2b) is int + doAssert toSeq(lambdaIter iota3()) == @[0, 1, 2] + doAssert toSeq(lambdaIter iota3()) == @[0, 1, 2] + doAssert toSeq(lambdaIter iota3Bis()) == @[0, 1, 2] # import test + doAssert toSeq(lambdaIter myiter2b) == @[0, 1, 2] + # make sure restarts from beginning + doAssert toSeq(lambdaIter myiter2b) == @[0,1,2] + doAssert toSeq(@[2,3,4]) == @[2,3,4] + + let s0 = default(elementType(myiter2a())) + doAssert toSeq(lambdaIter iota3()) == @[0, 1, 2] + + block: + const i1 = lambdaIter iota3() + doAssert type(i1) is int + doAssert typeof(i1) is int + doAssert toSeq(alias2 i1) == @[0, 1, 2] + + const i2 = lambdaIter iterAux(lambdaIter iota3()) + doAssert type(i2) is int + doAssert typeof(i2) is int + + block: # D20190818T125934:here D20190816T205304:here + const i3 = alias2 iterAux4(lambdaIter iota3()) + static: doAssert type(i3) is iterator + static: doAssert typeof(i3) is iterator + static: doAssert type(i3()) is int + var ret: seq[int] + for ai in i3(): ret.add ai + doAssert ret == @[0, 10, 20] + + block: + const i3 = lambdaIter iterAux4(lambdaIter iota3())() + static: doAssert type(i3) is int + static: doAssert type(i3()) is int + static: doAssert typeof(i3) is int + doAssert toSeq(alias2 i3) == @[0, 10, 20] + + block: + const i5 = alias2 iterAux5(lambdaIter iota3(), lambdaIter iota3()) + doAssert toSeq(alias2 i5) == @[0, 1, 2, 0, 1, 2] + +proc testArrowWrongSym2() = + # similar to `testArrowWrongSym` + template a(): untyped = discard # distractor + template c(): untyped = discard # distractor + doAssert toSeq(lambdaIter map(lambdaIter map(lambdaIter iota(3), a~>a*10), c~>c*4)) == @[0, 1*10*4, 2*10*4] + +proc testAll*() = + testIterator() + testIterator2() + testIssue4516() + testArrowWrongSym2() + +testAll() + +{.pop.}