From fee2a7ecfa883d100e1177b1cc7d738c6cfeaa83 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Thu, 22 Aug 2013 19:22:28 +0300 Subject: [PATCH 1/5] Experimental support for delayed instantiation of generics This postpones the semantic pass over the generic's body until the generic is instantiated. There are several pros and cons for this method and the capabilities that it enables may still be possible in the old framework if we teach it a few new trick. Such an attempt will follow in the next commits. pros: 1) It allows macros to be expanded during generic instantiation that will provide the body of the generic. See ``tmacrogenerics``. 2) The instantiation code is dramatically simplified. Dealing with unknown types in the generic's body pre-pass requires a lot of hacky code and error silencing in semTypeNode. See ``tgenericshardcases``. cons: 1) There is a performance penalty of roughly 5% when bootstrapping. 2) Certain errors that used to be detected in the previous pre-pass won't be detected with the new scheme until instantiation. --- compiler/ast.nim | 7 ++-- compiler/evals.nim | 20 +++++------- compiler/options.nim | 2 ++ compiler/semfold.nim | 16 ++++++--- compiler/seminst.nim | 49 ++++++++++++++++++++++++---- compiler/semmagic.nim | 2 +- compiler/semstmts.nim | 16 +++++---- compiler/semtypes.nim | 11 ++++--- compiler/semtypinst.nim | 4 +-- compiler/sigmatch.nim | 3 +- tests/compile/tgenericshardcases.nim | 30 +++++++++++++++++ tests/run/tmacrogenerics.nim | 39 ++++++++++++++++++++++ 12 files changed, 161 insertions(+), 38 deletions(-) create mode 100644 tests/compile/tgenericshardcases.nim create mode 100644 tests/run/tmacrogenerics.nim diff --git a/compiler/ast.nim b/compiler/ast.nim index bb015ea274..bb06e7163d 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -626,6 +626,7 @@ type case kind*: TSymKind of skType: typeInstCache*: seq[PType] + typScope*: PScope of routineKinds: procInstCache*: seq[PInstantiation] scope*: PScope # the scope where the proc was defined @@ -799,9 +800,9 @@ const # imported via 'importc: "fullname"' and no format string. # creator procs: -proc NewSym*(symKind: TSymKind, Name: PIdent, owner: PSym, +proc newSym*(symKind: TSymKind, Name: PIdent, owner: PSym, info: TLineInfo): PSym -proc NewType*(kind: TTypeKind, owner: PSym): PType +proc newType*(kind: TTypeKind, owner: PSym): PType proc newNode*(kind: TNodeKind): PNode proc newIntNode*(kind: TNodeKind, intVal: BiggestInt): PNode proc newIntTypeNode*(kind: TNodeKind, intVal: BiggestInt, typ: PType): PNode @@ -1111,7 +1112,7 @@ proc copySym(s: PSym, keepId: bool = false): PSym = result.loc = s.loc result.annex = s.annex # BUGFIX -proc NewSym(symKind: TSymKind, Name: PIdent, owner: PSym, +proc newSym(symKind: TSymKind, Name: PIdent, owner: PSym, info: TLineInfo): PSym = # generates a symbol and initializes the hash field too new(result) diff --git a/compiler/evals.nim b/compiler/evals.nim index 3f09664a79..35954cceca 100644 --- a/compiler/evals.nim +++ b/compiler/evals.nim @@ -904,18 +904,16 @@ proc evalParseStmt(c: PEvalContext, n: PNode): PNode = code.info.line.int) #result.typ = newType(tyStmt, c.module) -proc evalTypeTrait*(n: PNode, context: PSym): PNode = - ## XXX: This should be pretty much guaranteed to be true - # by the type traits procs' signatures, but until the - # code is more mature it doesn't hurt to be extra safe - internalAssert n.sons.len >= 2 and n.sons[1].kind == nkSym +proc evalTypeTrait*(trait, operand: PNode, context: PSym): PNode = + InternalAssert operand.kind == nkSym and + operand.sym.typ.kind == tyTypeDesc - let typ = n.sons[1].sym.typ.skipTypes({tyTypeDesc}) - case n.sons[0].sym.name.s.normalize + let typ = operand.sym.typ.skipTypes({tyTypeDesc}) + case trait.sym.name.s.normalize of "name": - result = newStrNode(nkStrLit, typ.typeToString(preferExported)) + result = newStrNode(nkStrLit, typ.typeToString(preferName)) result.typ = newType(tyString, context) - result.info = n.info + result.info = trait.info else: internalAssert false @@ -1037,8 +1035,8 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode = of mParseStmtToAst: result = evalParseStmt(c, n) of mExpandToAst: result = evalExpandToAst(c, n) of mTypeTrait: - n.sons[1] = evalAux(c, n.sons[1], {}) - result = evalTypeTrait(n, c.module) + let operand = evalAux(c, n.sons[1], {}) + result = evalTypeTrait(n[0], operand, c.module) of mIs: n.sons[1] = evalAux(c, n.sons[1], {}) result = evalIsOp(n) diff --git a/compiler/options.nim b/compiler/options.nim index 5f173d240c..cfda15f6a1 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -156,6 +156,8 @@ var const oKeepVariableNames* = true +const oUseLateInstantiation* = true + proc mainCommandArg*: string = ## This is intended for commands like check or parse ## which will work on the main project file unless diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 77ecf2e979..ed7b146849 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -558,9 +558,10 @@ proc getConstExpr(m: PSym, n: PNode): PNode = case n.kind of nkSym: var s = n.sym - if s.kind == skEnumField: + case s.kind + of skEnumField: result = newIntNodeT(s.position, n) - elif s.kind == skConst: + of skConst: case s.magic of mIsMainModule: result = newIntNodeT(ord(sfMainModule in m.flags), n) of mCompileDate: result = newStrNodeT(times.getDateStr(), n) @@ -578,10 +579,17 @@ proc getConstExpr(m: PSym, n: PNode): PNode = of mNegInf: result = newFloatNodeT(NegInf, n) else: if sfFakeConst notin s.flags: result = copyTree(s.ast) - elif s.kind in {skProc, skMethod}: # BUGFIX + of {skProc, skMethod}: result = n - elif s.kind in {skType, skGenericParam}: + of skType: result = newSymNodeTypeDesc(s, n.info) + of skGenericParam: + if s.typ.kind == tyExpr: + result = s.typ.n + result.typ = s.typ.sons[0] + else: + result = newSymNodeTypeDesc(s, n.info) + else: nil of nkCharLit..nkNilLit: result = copyNode(n) of nkIfExpr: diff --git a/compiler/seminst.nim b/compiler/seminst.nim index 601072833d..0cf5086a8d 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -130,13 +130,50 @@ proc sideEffectsCheck(c: PContext, s: PSym) = s.ast.sons[genericParamsPos].kind == nkEmpty: c.threadEntries.add(s) +proc lateInstantiateGeneric(c: PContext, invocation: PType, info: TLineInfo): PType = + InternalAssert invocation.kind == tyGenericInvokation + + let cacheHit = searchInstTypes(invocation) + if cacheHit != nil: + result = cacheHit + else: + let s = invocation.sons[0].sym + let oldScope = c.currentScope + c.currentScope = s.typScope + openScope(c) + pushInfoContext(info) + for i in 0 .. 0): # This is either a type known to sem or a typedesc # param to a regular proc (again, known at instantiation) - result = evalTypeTrait(n, GetCurrOwner()) + result = evalTypeTrait(n[0], n[1], GetCurrOwner()) else: # a typedesc variable, pass unmodified to evals result = n diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 33c0adac1d..a15b3e10a6 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -729,12 +729,16 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) = # like: mydata.seq rawAddSon(s.typ, newTypeS(tyEmpty, c)) s.ast = a - inc c.InGenericContext - var body = semTypeNode(c, a.sons[2], nil) - dec c.InGenericContext - if body != nil: - body.sym = s - body.size = -1 # could not be computed properly + when oUseLateInstantiation: + var body: PType = nil + s.typScope = c.currentScope.parent + else: + inc c.InGenericContext + var body = semTypeNode(c, a.sons[2], nil) + dec c.InGenericContext + if body != nil: + body.sym = s + body.size = -1 # could not be computed properly s.typ.sons[sonsLen(s.typ) - 1] = body popOwner() closeScope(c) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 8ae23f851d..1f7574bb5f 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -156,7 +156,7 @@ proc semRangeAux(c: PContext, n: PNode, prev: PType): PType = LocalError(n.Info, errRangeIsEmpty) var a = semConstExpr(c, n[1]) var b = semConstExpr(c, n[2]) - if not sameType(a.typ, b.typ): + if not sameType(a.typ, b.typ): LocalError(n.info, errPureTypeMismatch) elif a.typ.kind notin {tyInt..tyInt64,tyEnum,tyBool,tyChar, tyFloat..tyFloat128,tyUInt8..tyUInt32}: @@ -204,8 +204,8 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType = indx = e.typ.skipTypes({tyTypeDesc}) addSonSkipIntLit(result, indx) if indx.kind == tyGenericInst: indx = lastSon(indx) - if indx.kind != tyGenericParam: - if not isOrdinalType(indx): + if indx.kind != tyGenericParam: + if not isOrdinalType(indx): LocalError(n.sons[1].info, errOrdinalTypeExpected) elif enumHasHoles(indx): LocalError(n.sons[1].info, errEnumXHasHoles, indx.sym.name.s) @@ -846,7 +846,10 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = LocalError(n.info, errCannotInstantiateX, s.name.s) result = newOrPrevType(tyError, prev, c) else: - result = instGenericContainer(c, n, result) + when oUseLateInstantiation: + result = lateInstantiateGeneric(c, result, n.info) + else: + result = instGenericContainer(c, n, result) proc semTypeExpr(c: PContext, n: PNode): PType = var n = semExprWithType(c, n, {efDetermineType}) diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 31fbc33e1e..5482d5df8f 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -31,7 +31,7 @@ proc checkConstructedType*(info: TLineInfo, typ: PType) = if t.sons[0].kind != tyObject or tfFinal in t.sons[0].flags: localError(info, errInheritanceOnlyWithNonFinalObjects) -proc searchInstTypes(key: PType): PType = +proc searchInstTypes*(key: PType): PType = let genericTyp = key.sons[0] InternalAssert genericTyp.kind == tyGenericBody and key.sons[0] == genericTyp and @@ -55,7 +55,7 @@ proc searchInstTypes(key: PType): PType = return inst -proc cacheTypeInst(inst: PType) = +proc cacheTypeInst*(inst: PType) = # XXX: add to module's generics # update the refcount let genericTyp = inst.sons[0] diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 626d16d64a..fa87f27a7b 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -654,7 +654,7 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation = result = typeRel(c, x, a) # check if it fits of tyTypeDesc: var prev = PType(idTableGet(c.bindings, f)) - if prev == nil: + if prev == nil or true: if a.kind == tyTypeDesc: if f.sonsLen == 0: result = isGeneric @@ -768,6 +768,7 @@ proc ParamTypesMatchAux(c: PContext, m: var TCandidate, f, a: PType, if evaluated != nil: r = isGeneric arg.typ = newTypeS(tyExpr, c) + arg.typ.sons = @[evaluated.typ] arg.typ.n = evaluated if r == isGeneric: diff --git a/tests/compile/tgenericshardcases.nim b/tests/compile/tgenericshardcases.nim new file mode 100644 index 0000000000..90981c7014 --- /dev/null +++ b/tests/compile/tgenericshardcases.nim @@ -0,0 +1,30 @@ +discard """ + file: "tgenericshardcases.nim" + output: "int\nfloat\nint\nstring" +""" + +import typetraits + +proc typeNameLen(x: typedesc): int {.compileTime.} = + result = x.name.len + +macro selectType(a, b: typedesc): typedesc = + result = a + +type + Foo[T] = object + data1: array[high(T), int] + data2: array[1..typeNameLen(T), selectType(float, string)] + + MyEnum = enum A, B, C,D + +var f1: Foo[MyEnum] +var f2: Foo[int8] + +static: + assert high(f1.data1) == D + assert high(f1.data2) == 6 # length of MyEnum + + assert high(f2.data1) == 127 + assert high(f2.data2) == 4 # length of int8 + diff --git a/tests/run/tmacrogenerics.nim b/tests/run/tmacrogenerics.nim new file mode 100644 index 0000000000..5ae59e0da4 --- /dev/null +++ b/tests/run/tmacrogenerics.nim @@ -0,0 +1,39 @@ +discard """ + file: "tmacrogenerics.nim" + msg: ''' +instantiation 1 with int and float +instantiation 2 with float and string +instantiation 3 with string and string +counter: 3 +''' + output: "int\nfloat\nint\nstring" +""" + +import typetraits, macros + +var counter {.compileTime.} = 0 + +macro makeBar(A, B: typedesc): typedesc = + inc counter + echo "instantiation ", counter, " with ", A.name, " and ", B.name + result = A + +type + Bar[T, U] = makeBar(T, U) + +var bb1: Bar[int, float] +var bb2: Bar[float, string] +var bb3: Bar[int, float] +var bb4: Bar[string, string] + +proc match(a: int) = echo "int" +proc match(a: string) = echo "string" +proc match(a: float) = echo "float" + +match(bb1) +match(bb2) +match(bb3) +match(bb4) + +static: + echo "counter: ", counter From 8682ed9bd0bd8230e779e45cc65c2bfd4661a966 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 23 Aug 2013 00:42:43 +0300 Subject: [PATCH 2/5] pass-through of static int generic params to arrays when late instantiation is disabled --- compiler/evals.nim | 3 +-- compiler/options.nim | 2 +- compiler/semdata.nim | 10 +++++----- compiler/semexprs.nim | 2 +- compiler/semtypes.nim | 14 ++++++++------ compiler/semtypinst.nim | 6 ++++++ compiler/sigmatch.nim | 3 ++- tests/run/tstaticparams.nim | 9 +++++++-- 8 files changed, 31 insertions(+), 18 deletions(-) diff --git a/compiler/evals.nim b/compiler/evals.nim index 35954cceca..053068ea43 100644 --- a/compiler/evals.nim +++ b/compiler/evals.nim @@ -905,8 +905,7 @@ proc evalParseStmt(c: PEvalContext, n: PNode): PNode = #result.typ = newType(tyStmt, c.module) proc evalTypeTrait*(trait, operand: PNode, context: PSym): PNode = - InternalAssert operand.kind == nkSym and - operand.sym.typ.kind == tyTypeDesc + InternalAssert operand.kind == nkSym let typ = operand.sym.typ.skipTypes({tyTypeDesc}) case trait.sym.name.s.normalize diff --git a/compiler/options.nim b/compiler/options.nim index cfda15f6a1..2b25b2650a 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -156,7 +156,7 @@ var const oKeepVariableNames* = true -const oUseLateInstantiation* = true +const oUseLateInstantiation* = false proc mainCommandArg*: string = ## This is intended for commands like check or parse diff --git a/compiler/semdata.nim b/compiler/semdata.nim index b9c32a680e..8b04f4af57 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -225,14 +225,14 @@ proc fillTypeS(dest: PType, kind: TTypeKind, c: PContext) = dest.owner = getCurrOwner() dest.size = - 1 -proc makeRangeType*(c: PContext, first, last: biggestInt, - info: TLineInfo): PType = +proc makeRangeType*(c: PContext; first, last: biggestInt; + info: TLineInfo; intType = getSysType(tyInt)): PType = var n = newNodeI(nkRange, info) - addSon(n, newIntNode(nkIntLit, first)) - addSon(n, newIntNode(nkIntLit, last)) + addSon(n, newIntTypeNode(nkIntLit, first, intType)) + addSon(n, newIntTypeNode(nkIntLit, last, intType)) result = newTypeS(tyRange, c) result.n = n - rawAddSon(result, getSysType(tyInt)) # basetype of range + addSonSkipIntLit(result, intType) # basetype of range proc markIndirect*(c: PContext, s: PSym) {.inline.} = if s.kind in {skProc, skConverter, skMethod, skIterator}: diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index d88cc28b68..ebda315018 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -113,7 +113,7 @@ proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = of skGenericParam: if s.typ.kind == tyExpr: result = newSymNode(s, n.info) - result.typ = s.typ.lastSon + result.typ = s.typ elif s.ast != nil: result = semExpr(c, s.ast) else: diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 1f7574bb5f..e5d9058b48 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -195,16 +195,18 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType = else: let e = semExprWithType(c, n.sons[1], {efDetermineType}) if e.kind in {nkIntLit..nkUInt64Lit}: - indx = newTypeS(tyRange, c) - indx.n = newNodeI(nkRange, n.info) - addSon(indx.n, newIntTypeNode(e.kind, 0, e.typ)) - addSon(indx.n, newIntTypeNode(e.kind, e.intVal-1, e.typ)) - addSonSkipIntLit(indx, e.typ) + indx = makeRangeType(c, 0, e.intVal-1, n.info, e.typ) + elif e.kind == nkSym and e.typ.kind == tyExpr: + if e.sym.ast != nil: return semArray(c, e.sym.ast, nil) + InternalAssert c.InGenericContext > 0 + if not isOrdinalType(e.typ.lastSon): + localError(n[1].info, errOrdinalTypeExpected) + indx = e.typ else: indx = e.typ.skipTypes({tyTypeDesc}) addSonSkipIntLit(result, indx) if indx.kind == tyGenericInst: indx = lastSon(indx) - if indx.kind != tyGenericParam: + if indx.kind notin {tyGenericParam, tyExpr}: if not isOrdinalType(indx): LocalError(n.sons[1].info, errOrdinalTypeExpected) elif enumHasHoles(indx): diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 5482d5df8f..0c15c72482 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -208,6 +208,12 @@ proc ReplaceTypeVarsT*(cl: var TReplTypeVars, t: PType): PType = of tyInt: result = skipIntLit(t) else: + if t.kind == tyArray: + let idxt = t.sons[0] + if idxt.kind == tyExpr and + idxt.sym != nil and idxt.sym.kind == skGenericParam: + let value = lookupTypeVar(cl, idxt).n + t.sons[0] = makeRangeType(cl.c, 0, value.intVal - 1, value.info) if containsGenericType(t): result = copyType(t, t.owner, false) incl(result.flags, tfFromGeneric) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index fa87f27a7b..4483b1f8b2 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -622,7 +622,8 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation = of tyGenericParam, tyTypeClass: var x = PType(idTableGet(c.bindings, f)) if x == nil: - if c.calleeSym.kind == skType and f.kind == tyGenericParam and not c.typedescMatched: + if c.calleeSym != nil and c.calleeSym.kind == skType and + f.kind == tyGenericParam and not c.typedescMatched: # XXX: The fact that generic types currently use tyGenericParam for # their parameters is really a misnomer. tyGenericParam means "match # any value" and what we need is "match any type", which can be encoded diff --git a/tests/run/tstaticparams.nim b/tests/run/tstaticparams.nim index 3e501ed8b9..2327483563 100644 --- a/tests/run/tstaticparams.nim +++ b/tests/run/tstaticparams.nim @@ -1,17 +1,22 @@ discard """ file: "tstaticparams.nim" - output: "abracadabra\ntest" + output: "abracadabra\ntest\n3" """ type TFoo[T; Val: expr[string]] = object data: array[4, T] + TBar[T; I: expr[int]] = object + data: array[I, T] + proc takeFoo(x: TFoo) = echo "abracadabra" echo TFoo.Val var x: TFoo[int, "test"] - takeFoo(x) +var y: TBar[float, 4] +echo high(y.data) + From 56d75bd23ba8e302277614aa5e0ec1ae002fb56d Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 23 Aug 2013 15:43:27 +0300 Subject: [PATCH 3/5] implemented and documented the new typedesc binding rules --- compiler/semtypes.nim | 12 +++-- compiler/sigmatch.nim | 6 ++- doc/manual.txt | 33 ++++++++++--- lib/system.nim | 23 ++++++--- tests/compile/tbindtypedesc.nim | 87 +++++++++++++++++++++++++++++++++ 5 files changed, 143 insertions(+), 18 deletions(-) create mode 100644 tests/compile/tbindtypedesc.nim diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index e5d9058b48..b02fa7c318 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -589,6 +589,8 @@ proc addParamOrResult(c: PContext, param: PSym, kind: TSymKind) = else: if sfGenSym notin param.flags: addDecl(c, param) +let typedescId = getIdent"typedesc" + proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, paramType: PType, paramName: string, info: TLineInfo, anon = false): PType = @@ -636,6 +638,9 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, result = addImplicitGeneric(c.newTypeWithSons(tyExpr, paramType.sons)) of tyTypeDesc: if tfUnresolved notin paramType.flags: + # naked typedescs are not bindOnce types + if paramType.sonsLen == 0 and paramTypId != nil and + paramTypId.id == typedescId.id: paramTypId = nil result = addImplicitGeneric(c.newTypeWithSons(tyTypeDesc, paramType.sons)) of tyDistinct: if paramType.sonsLen == 1: @@ -762,7 +767,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, r.flags.incl tfRetType result.sons[0] = skipIntLit(r) res.typ = result.sons[0] - + proc semStmtListType(c: PContext, n: PNode, prev: PType): PType = checkMinSonsLen(n, 1) var length = sonsLen(n) @@ -1078,8 +1083,9 @@ proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode = if constraint.kind != nkEmpty: typ = semTypeNode(c, constraint, nil) if typ.kind != tyExpr or typ.len == 0: - if typ.len == 0 and typ.kind == tyTypeDesc: - typ = newTypeS(tyGenericParam, c) + if typ.kind == tyTypeDesc: + if typ.len == 0: + typ = newTypeS(tyTypeDesc, c) else: typ = semGenericConstraints(c, typ) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 4483b1f8b2..5766aa164b 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -655,7 +655,7 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation = result = typeRel(c, x, a) # check if it fits of tyTypeDesc: var prev = PType(idTableGet(c.bindings, f)) - if prev == nil or true: + if prev == nil: if a.kind == tyTypeDesc: if f.sonsLen == 0: result = isGeneric @@ -667,7 +667,9 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation = result = isNone else: InternalAssert prev.sonsLen == 1 - result = typeRel(c, prev.sons[0], a) + let toMatch = if tfUnresolved in f.flags: a + else: a.sons[0] + result = typeRel(c, prev.sons[0], toMatch) of tyExpr, tyStmt: result = isGeneric of tyProxy: diff --git a/doc/manual.txt b/doc/manual.txt index d2fc7e5c9a..0a20099615 100644 --- a/doc/manual.txt +++ b/doc/manual.txt @@ -1451,7 +1451,7 @@ But it seems all this boilerplate code needs to be repeated for the ``TEuro`` currency. This can be solved with templates_. .. code-block:: nimrod - template Additive(typ: typeDesc): stmt = + template Additive(typ: typedesc): stmt = proc `+` *(x, y: typ): typ {.borrow.} proc `-` *(x, y: typ): typ {.borrow.} @@ -1459,13 +1459,13 @@ currency. This can be solved with templates_. proc `+` *(x: typ): typ {.borrow.} proc `-` *(x: typ): typ {.borrow.} - template Multiplicative(typ, base: typeDesc): stmt = + template Multiplicative(typ, base: typedesc): stmt = 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): stmt = + template Comparable(typ: typedesc): stmt = proc `<` * (x, y: typ): bool {.borrow.} proc `<=` * (x, y: typ): bool {.borrow.} proc `==` * (x, y: typ): bool {.borrow.} @@ -3323,10 +3323,10 @@ The template body does not open a new scope. To open a new scope a ``block`` statement can be used: .. code-block:: nimrod - template declareInScope(x: expr, t: typeDesc): stmt {.immediate.} = + template declareInScope(x: expr, t: typedesc): stmt {.immediate.} = var x: t - template declareInNewScope(x: expr, t: typeDesc): stmt {.immediate.} = + template declareInNewScope(x: expr, t: typedesc): stmt {.immediate.} = # open a new scope: block: var x: t @@ -3419,7 +3419,7 @@ In templates identifiers can be constructed with the backticks notation: .. code-block:: nimrod - template typedef(name: expr, typ: typeDesc) {.immediate.} = + template typedef(name: expr, typ: typedesc) {.immediate.} = type `T name`* {.inject.} = typ `P name`* {.inject.} = ref `T name` @@ -3480,7 +3480,7 @@ template cannot be accessed in the instantiation context: .. code-block:: nimrod - template newException*(exceptn: typeDesc, message: string): expr = + template newException*(exceptn: typedesc, message: string): expr = var e: ref exceptn # e is implicitly gensym'ed here new(e) @@ -3728,6 +3728,25 @@ instantiation type using the param name: var n = TNode.new var tree = new(TBinaryTree[int]) +When multiple typedesc params are present, they act like a distinct type class +(i.e. they will bind freely to different types). To force a bind-once behavior +one can use a named alias or an explicit `typedesc` generic param: + +.. code-block:: nimrod + + # `type1` and `type2` are aliases for typedesc available from system.nim + proc acceptOnlyTypePairs(A, B: type1; C, D: type2) + proc acceptOnlyTypePairs[T: typedesc, U: typedesc](A, B: T; C, D: U) + +Once bound, typedesc params can appear in the rest of the proc signature: + +.. code-block:: nimrod + + template declareVariableWithType(T: typedesc, value: T) = + var x: T = value + + declareVariableWithType int, 42 + When used with macros and .compileTime. procs on the other hand, the compiler does not need to instantiate the code multiple times, because types then can be manipulated using the unified internal symbol representation. In such context diff --git a/lib/system.nim b/lib/system.nim index 08e4c367ba..749124ac9c 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -52,7 +52,7 @@ type `nil` {.magic: "Nil".} expr* {.magic: Expr.} ## meta type to denote an expression (for templates) stmt* {.magic: Stmt.} ## meta type to denote a statement (for templates) - typeDesc* {.magic: TypeDesc.} ## meta type to denote a type description + typedesc* {.magic: TypeDesc.} ## meta type to denote a type description void* {.magic: "VoidType".} ## meta type to denote the absense of any type auto* = expr any* = distinct auto @@ -76,6 +76,17 @@ type TNumber* = TInteger|TReal ## type class matching all number types +type + ## helper types for writing implicitly generic procs + T1* = expr + T2* = expr + T3* = expr + T4* = expr + T5* = expr + type1* = typedesc + type2* = typedesc + type3* = typedesc + proc defined*(x: expr): bool {.magic: "Defined", noSideEffect.} ## Special compile-time procedure that checks whether `x` is ## defined. `x` has to be an identifier or a qualified identifier. @@ -1473,7 +1484,7 @@ when not defined(NimrodVM): proc seqToPtr[T](x: seq[T]): pointer {.noStackFrame, nosideeffect.} = asm """return `x`""" - proc `==` *[T: typeDesc](x, y: seq[T]): bool {.noSideEffect.} = + proc `==` *[T](x, y: seq[T]): bool {.noSideEffect.} = ## Generic equals operator for sequences: relies on a equals operator for ## the element type `T`. if seqToPtr(x) == seqToPtr(y): @@ -1485,7 +1496,7 @@ when not defined(NimrodVM): if x[i] != y[i]: return false result = true -proc find*[T, S: typeDesc](a: T, item: S): int {.inline.}= +proc find*[T, S](a: T, item: S): int {.inline.}= ## Returns the first index of `item` in `a` or -1 if not found. This requires ## appropriate `items` and `==` operations to work. for i in items(a): @@ -1788,7 +1799,7 @@ proc debugEcho*[T](x: varargs[T, `$`]) {.magic: "Echo", noSideEffect, ## to be free of side effects, so that it can be used for debugging routines ## marked as ``noSideEffect``. -template newException*(exceptn: typeDesc, message: string): expr = +template newException*(exceptn: typedesc, message: string): expr = ## creates an exception object of type ``exceptn`` and sets its ``msg`` field ## to `message`. Returns the new exception object. var @@ -1801,7 +1812,7 @@ when hostOS == "standalone": include panicoverride when not defined(sysFatal): - template sysFatal(exceptn: typeDesc, message: string) = + template sysFatal(exceptn: typedesc, message: string) = when hostOS == "standalone": panic(message) else: @@ -1810,7 +1821,7 @@ when not defined(sysFatal): e.msg = message raise e - template sysFatal(exceptn: typeDesc, message, arg: string) = + template sysFatal(exceptn: typedesc, message, arg: string) = when hostOS == "standalone": rawoutput(message) panic(arg) diff --git a/tests/compile/tbindtypedesc.nim b/tests/compile/tbindtypedesc.nim new file mode 100644 index 0000000000..dd4ef854cc --- /dev/null +++ b/tests/compile/tbindtypedesc.nim @@ -0,0 +1,87 @@ +discard """ + msg: ''' +int +float +TFoo +TFoo +''' +""" + +import typetraits + +type + TFoo = object + x, y: int + + TBar = tuple + x, y: int + +template good(e: expr) = + static: assert(compiles(e)) + +template bad(e: expr) = + static: assert(not compiles(e)) + +proc genericParamRepeated[T: typedesc](a: T, b: T) = + static: + echo a.name + echo b.name + +good(genericParamRepeated(int, int)) +good(genericParamRepeated(float, float)) + +bad(genericParamRepeated(string, int)) +bad(genericParamRepeated(int, float)) + +proc genericParamOnce[T: typedesc](a, b: T) = + static: + echo a.name + echo b.name + +good(genericParamOnce(int, int)) +good(genericParamOnce(TFoo, TFoo)) + +bad(genericParamOnce(string, int)) +bad(genericParamOnce(TFoo, float)) + +proc typePairs(A, B: type1; C, D: type2) = nil + +good(typePairs(int, int, TFoo, TFOO)) +good(typePairs(TBAR, TBar, TBAR, TBAR)) +good(typePairs(int, int, string, string)) + +bad(typePairs(TBAR, TBar, TBar, TFoo)) +bad(typePairs(string, int, TBAR, TBAR)) + +proc typePairs2[T: typedesc, U: typedesc](A, B: T; C, D: U) = nil + +good(typePairs2(int, int, TFoo, TFOO)) +good(typePairs2(TBAR, TBar, TBAR, TBAR)) +good(typePairs2(int, int, string, string)) + +bad(typePairs2(TBAR, TBar, TBar, TFoo)) +bad(typePairs2(string, int, TBAR, TBAR)) + +proc dontBind(a: typedesc, b: typedesc) = + static: + echo a.name + echo b.name + +good(dontBind(int, float)) +good(dontBind(TFoo, TFoo)) + +proc dontBind2(a, b: typedesc) = nil + +good(dontBind2(int, float)) +good(dontBind2(TBar, int)) + +proc bindArg(T: typedesc, U: typedesc, a, b: T, c, d: U) = nil + +good(bindArg(int, string, 10, 20, "test", "nest")) +good(bindArg(int, int, 10, 20, 30, 40)) + +bad(bindArg(int, string, 10, "test", "test", "nest")) +bad(bindArg(int, int, 10, 20, 30, "test")) +bad(bindArg(int, string, 10.0, 20, "test", "nest")) +bad(bindArg(int, string, "test", "nest", 10, 20)) + From 18033026cbc9b033bcbc1d6e62a4d7fced07feee Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Fri, 23 Aug 2013 23:14:27 +0100 Subject: [PATCH 4/5] Removed outdated bootstrapping info from install.txt --- install.txt | 43 ------------------------------------------- readme.md | 5 ++--- readme.txt | 5 ++--- 3 files changed, 4 insertions(+), 49 deletions(-) diff --git a/install.txt b/install.txt index eefdfb6827..11c502235a 100644 --- a/install.txt +++ b/install.txt @@ -62,46 +62,3 @@ Currently, the following C compilers are supported under Windows: | http://www.digitalmars.com/download/freecompiler.html However, most testing is done with GCC. - - - -Bootstrapping from github -------------------------- - -To get the source code you need either of these: - -* A working web browser + tar(or equivalent): - https://github.com/Araq/Nimrod/tarball/master -* wget + tar: - ``wget --no-check-certificate "https://github.com/Araq/Nimrod/tarball/master"`` -* git: ``git clone git://github.com/Araq/Nimrod.git`` - -After downloading the source (and extracting it), you need to -extract ``build/csources.zip``: - -* ``cd build`` -* ``unzip csources.zip`` -* ``cd ..`` - -and then you can bootstrap with: - -On Windows -~~~~~~~~~~ - -* ``build.bat`` -* ``bin\nimrod c koch`` -* ``koch boot -d:release`` - -If you want a 64 bit build, make sure that you have a GCC built for Win64 and -execute ``build64.bat`` instead of ``build.bat``. - - -On UNIX -~~~~~~~ - -* ``./build.sh`` -* ``bin/nimrod c koch`` -* ``./koch boot -d:release`` - -Installation on UNIX can then be done with ``koch install [dir]``. - diff --git a/readme.md b/readme.md index 59546653b0..f74f812839 100644 --- a/readme.md +++ b/readme.md @@ -6,8 +6,7 @@ documentation. Compiling the Nimrod compiler is quite straightforward. Because the Nimrod compiler itself is written in the Nimrod programming language the C source of an older version of the compiler are needed to bootstrap the -latest version. The C sources are however included with this repository under -the build directory. +latest version. The C sources are available in a separate repo [here](http://github.com/nimrod-code/csources). Pre-compiled snapshots of the compiler are also available on [Nimbuild](http://build.nimrod-code.org/). Your platform however may not @@ -53,7 +52,7 @@ and you can also get help in the IRC channel on [Freenode](irc://irc.freenode.net/nimrod) in #nimrod. ## License -The compiler and the standard library is licensed under the MIT license, +The compiler and the standard library are licensed under the MIT license, except for some modules where the documentation suggests otherwise. This means that you can use any license for your own programs developed with Nimrod, allowing you to create commercial applications. diff --git a/readme.txt b/readme.txt index 59546653b0..f74f812839 100644 --- a/readme.txt +++ b/readme.txt @@ -6,8 +6,7 @@ documentation. Compiling the Nimrod compiler is quite straightforward. Because the Nimrod compiler itself is written in the Nimrod programming language the C source of an older version of the compiler are needed to bootstrap the -latest version. The C sources are however included with this repository under -the build directory. +latest version. The C sources are available in a separate repo [here](http://github.com/nimrod-code/csources). Pre-compiled snapshots of the compiler are also available on [Nimbuild](http://build.nimrod-code.org/). Your platform however may not @@ -53,7 +52,7 @@ and you can also get help in the IRC channel on [Freenode](irc://irc.freenode.net/nimrod) in #nimrod. ## License -The compiler and the standard library is licensed under the MIT license, +The compiler and the standard library are licensed under the MIT license, except for some modules where the documentation suggests otherwise. This means that you can use any license for your own programs developed with Nimrod, allowing you to create commercial applications. From c934a33ccd85716b66d44bca6a1764204507f33e Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Mon, 26 Aug 2013 18:02:45 +0100 Subject: [PATCH 5/5] Implemented ability to connect through proxies for the httpclient module. --- lib/pure/httpclient.nim | 67 +++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index 15cc6abb83..2c0e7b8351 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -10,6 +10,11 @@ ## This module implements a simple HTTP client that can be used to retrieve ## webpages/other data. ## +## +## **Note**: This module is not ideal, connection is not kept alive so sites with +## many redirects are expensive. As such in the future this module may change, +## and the current procedures will be deprecated. +## ## Retrieving a website ## ==================== ## @@ -62,8 +67,15 @@ ## that as long as the server is sending data an exception will not be raised, ## if however data does not reach client within the specified timeout an ETimeout ## exception will then be raised. +## +## Proxy +## ===== +## +## A proxy can be specified as a param to any of these procedures, the ``newProxy`` +## constructor should be used for this purpose. However, +## currently only basic authentication is supported. -import sockets, strutils, parseurl, parseutils, strtabs +import sockets, strutils, parseurl, parseutils, strtabs, base64 type TResponse* = tuple[ @@ -72,6 +84,10 @@ type headers: PStringTable, body: string] + PProxy* = ref object + url*: TUrl + auth*: string + EInvalidProtocol* = object of ESynch ## exception that is raised when server ## does not conform to the implemented ## protocol @@ -239,23 +255,34 @@ when not defined(ssl): else: let defaultSSLContext = newContext(verifyMode = CVerifyNone) +proc newProxy*(url: string, auth = ""): PProxy = + ## Constructs a new ``TProxy`` object. + result = PProxy(url: parseUrl(url), auth: auth) + proc request*(url: string, httpMethod = httpGET, extraHeaders = "", body = "", sslContext: PSSLContext = defaultSSLContext, - timeout = -1, userAgent = defUserAgent): TResponse = + timeout = -1, userAgent = defUserAgent, + proxy: PProxy = nil): TResponse = ## | Requests ``url`` with the specified ``httpMethod``. ## | Extra headers can be specified and must be seperated by ``\c\L`` ## | An optional timeout can be specified in miliseconds, if reading from the ## server takes longer than specified an ETimeout exception will be raised. - var r = parseUrl(url) + var r = if proxy == nil: parseUrl(url) else: proxy.url var headers = substr($httpMethod, len("http")) - headers.add(" /" & r.path & r.query) + if proxy == nil: + headers.add(" /" & r.path & r.query) + else: + headers.add(" " & url) headers.add(" HTTP/1.1\c\L") add(headers, "Host: " & r.hostname & "\c\L") if userAgent != "": add(headers, "User-Agent: " & userAgent & "\c\L") + if proxy != nil and proxy.auth != "": + let auth = base64.encode(proxy.auth, newline = "") + add(headers, "Proxy-Authorization: basic " & auth & "\c\L") add(headers, extraHeaders) add(headers, "\c\L") @@ -299,30 +326,34 @@ proc getNewLocation(lastUrl: string, headers: PStringTable): string = proc get*(url: string, extraHeaders = "", maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext, - timeout = -1, userAgent = defUserAgent): TResponse = + timeout = -1, userAgent = defUserAgent, + proxy: PProxy = nil): TResponse = ## | GETs the ``url`` and returns a ``TResponse`` object ## | This proc also handles redirection ## | Extra headers can be specified and must be separated by ``\c\L``. ## | An optional timeout can be specified in miliseconds, if reading from the ## server takes longer than specified an ETimeout exception will be raised. - result = request(url, httpGET, extraHeaders, "", sslContext, timeout, userAgent) + result = request(url, httpGET, extraHeaders, "", sslContext, timeout, + userAgent, proxy) var lastURL = url for i in 1..maxRedirects: if result.status.redirection(): let redirectTo = getNewLocation(lastURL, result.headers) result = request(redirectTo, httpGET, extraHeaders, "", sslContext, - timeout, userAgent) + timeout, userAgent, proxy) lastUrl = redirectTo proc getContent*(url: string, extraHeaders = "", maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext, - timeout = -1, userAgent = defUserAgent): string = + timeout = -1, userAgent = defUserAgent, + proxy: PProxy = nil): string = ## | GETs the body and returns it as a string. ## | Raises exceptions for the status codes ``4xx`` and ``5xx`` ## | Extra headers can be specified and must be separated by ``\c\L``. ## | An optional timeout can be specified in miliseconds, if reading from the ## server takes longer than specified an ETimeout exception will be raised. - var r = get(url, extraHeaders, maxRedirects, sslContext, timeout, userAgent) + var r = get(url, extraHeaders, maxRedirects, sslContext, timeout, userAgent, + proxy) if r.status[0] in {'4','5'}: raise newException(EHTTPRequestErr, r.status) else: @@ -331,7 +362,8 @@ proc getContent*(url: string, extraHeaders = "", maxRedirects = 5, proc post*(url: string, extraHeaders = "", body = "", maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext, - timeout = -1, userAgent = defUserAgent): TResponse = + timeout = -1, userAgent = defUserAgent, + proxy: PProxy = nil): TResponse = ## | POSTs ``body`` to the ``url`` and returns a ``TResponse`` object. ## | This proc adds the necessary Content-Length header. ## | This proc also handles redirection. @@ -339,27 +371,29 @@ proc post*(url: string, extraHeaders = "", body = "", ## | An optional timeout can be specified in miliseconds, if reading from the ## server takes longer than specified an ETimeout exception will be raised. var xh = extraHeaders & "Content-Length: " & $len(body) & "\c\L" - result = request(url, httpPOST, xh, body, sslContext, timeout, userAgent) + result = request(url, httpPOST, xh, body, sslContext, timeout, userAgent, + proxy) var lastUrl = "" for i in 1..maxRedirects: if result.status.redirection(): let redirectTo = getNewLocation(lastURL, result.headers) var meth = if result.status != "307": httpGet else: httpPost result = request(redirectTo, meth, xh, body, sslContext, timeout, - userAgent) + userAgent, proxy) lastUrl = redirectTo proc postContent*(url: string, extraHeaders = "", body = "", maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext, - timeout = -1, userAgent = defUserAgent): string = + timeout = -1, userAgent = defUserAgent, + proxy: PProxy = nil): string = ## | POSTs ``body`` to ``url`` and returns the response's body as a string ## | Raises exceptions for the status codes ``4xx`` and ``5xx`` ## | Extra headers can be specified and must be separated by ``\c\L``. ## | An optional timeout can be specified in miliseconds, if reading from the ## server takes longer than specified an ETimeout exception will be raised. var r = post(url, extraHeaders, body, maxRedirects, sslContext, timeout, - userAgent) + userAgent, proxy) if r.status[0] in {'4','5'}: raise newException(EHTTPRequestErr, r.status) else: @@ -367,14 +401,15 @@ proc postContent*(url: string, extraHeaders = "", body = "", proc downloadFile*(url: string, outputFilename: string, sslContext: PSSLContext = defaultSSLContext, - timeout = -1, userAgent = defUserAgent) = + timeout = -1, userAgent = defUserAgent, + proxy: PProxy = nil) = ## | Downloads ``url`` and saves it to ``outputFilename`` ## | An optional timeout can be specified in miliseconds, if reading from the ## server takes longer than specified an ETimeout exception will be raised. var f: TFile if open(f, outputFilename, fmWrite): f.write(getContent(url, sslContext = sslContext, timeout = timeout, - userAgent = userAgent)) + userAgent = userAgent, proxy = proxy)) f.close() else: fileError("Unable to open file")