diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2017-05-17 15:27:04 +0200 |
---|---|---|
committer | Andreas Rumpf <rumpf_a@web.de> | 2017-05-17 15:27:04 +0200 |
commit | 61a0eba14ff311dff49457ed3cef955a97abc905 (patch) | |
tree | 7635ce2f48e76ab1c64f8900344cd05efb2727a1 | |
parent | 503f7806765f0cc6f072f578e272d12d3f9cce56 (diff) | |
parent | 672c24e4b8fcfc07cdba6a36a2fc0445cdc3d9e9 (diff) | |
download | Nim-61a0eba14ff311dff49457ed3cef955a97abc905.tar.gz |
Merge branch 'zahary' into araq2
-rw-r--r-- | compiler/ast.nim | 15 | ||||
-rw-r--r-- | compiler/ccgexprs.nim | 2 | ||||
-rw-r--r-- | compiler/condsyms.nim | 1 | ||||
-rw-r--r-- | compiler/msgs.nim | 2 | ||||
-rw-r--r-- | compiler/parser.nim | 9 | ||||
-rw-r--r-- | compiler/pbraces.nim | 5 | ||||
-rw-r--r-- | compiler/semcall.nim | 2 | ||||
-rw-r--r-- | compiler/semdata.nim | 4 | ||||
-rw-r--r-- | compiler/semexprs.nim | 31 | ||||
-rw-r--r-- | compiler/semmagic.nim | 48 | ||||
-rw-r--r-- | compiler/semstmts.nim | 101 | ||||
-rw-r--r-- | compiler/semtypes.nim | 19 | ||||
-rw-r--r-- | compiler/sigmatch.nim | 127 | ||||
-rw-r--r-- | compiler/vm.nim | 5 | ||||
-rw-r--r-- | doc/manual/type_rel.txt | 101 | ||||
-rw-r--r-- | lib/core/macros.nim | 2 | ||||
-rw-r--r-- | lib/system.nim | 17 | ||||
-rw-r--r-- | tests/cpp/tcovariancerules.nim | 424 | ||||
-rw-r--r-- | tests/errmsgs/tinvalidinout.nim | 26 | ||||
-rw-r--r-- | tests/generics/tfakecovariance.nim | 78 | ||||
-rw-r--r-- | tests/generics/tgenericconst.nim | 39 | ||||
-rw-r--r-- | tests/generics/tptrinheritance.nim | 2 | ||||
-rw-r--r-- | tests/stdlib/tmarshal.nim | 24 | ||||
-rw-r--r-- | tests/template/tgenerictemplates.nim | 13 | ||||
-rw-r--r-- | web/bountysource.nim | 4 |
25 files changed, 1017 insertions, 84 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index 5b68e9712..ee6391919 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -212,6 +212,8 @@ type nkIteratorTy, # iterator type nkSharedTy, # 'shared T' # we use 'nkPostFix' for the 'not nil' addition + nkInTy, # prefix `in` used to mark contravariant types + nkOutTy, # prefix `out` used to mark covariant types nkEnumTy, # enum body nkEnumFieldDef, # `ident = expr` in an enumeration nkArgList, # argument list @@ -268,6 +270,9 @@ type sfDiscardable, # returned value may be discarded implicitly sfOverriden, # proc is overriden sfGenSym # symbol is 'gensym'ed; do not add to symbol table + sfCovariant # covariant generic param mimicing a ptr type + sfWeakCovariant # covariant generic param mimicing a seq/array type + sfContravariant # contravariant generic param TSymFlags* = set[TSymFlag] @@ -1004,15 +1009,17 @@ proc add*(father, son: PNode) = if isNil(father.sons): father.sons = @[] add(father.sons, son) -proc `[]`*(n: PNode, i: int): PNode {.inline.} = - result = n.sons[i] +type Indexable = PNode | PType + +template `[]`*(n: Indexable, i: int): Indexable = + n.sons[i] template `-|`*(b, s: untyped): untyped = (if b >= 0: b else: s.len + b) # son access operators with support for negative indices -template `{}`*(n: PNode, i: int): untyped = n[i -| n] -template `{}=`*(n: PNode, i: int, s: PNode) = +template `{}`*(n: Indexable, i: int): untyped = n[i -| n] +template `{}=`*(n: Indexable, i: int, s: Indexable) = n.sons[i -| n] = s when defined(useNodeIds): diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index f981f9595..f2c6f076c 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -2203,7 +2203,7 @@ proc getNullValueAux(p: BProc; obj, cons: PNode, result: var Rope) = let field = obj.sym for i in 1..<cons.len: if cons[i].kind == nkExprColonExpr: - if cons[i][0].sym == field: + if cons[i][0].sym.name == field.name: result.add genConstExpr(p, cons[i][1]) return elif i == field.position: diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index 4303fd6c8..a4f47ac72 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -103,3 +103,4 @@ proc initDefines*() = defineSymbol("nimNewShiftOps") defineSymbol("nimDistros") defineSymbol("nimHasCppDefine") + defineSymbol("nimGenericInOutFlags") diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 3a97f1ed2..e416a3826 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -113,6 +113,7 @@ type errGenericLambdaNotAllowed, errProcHasNoConcreteType, errCompilerDoesntSupportTarget, + errInOutFlagNotExtern, errUser, warnCannotOpenFile, warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit, @@ -380,6 +381,7 @@ const "of the generic paramers can be inferred from the expected signature.", errProcHasNoConcreteType: "'$1' doesn't have a concrete type, due to unspecified generic parameters.", errCompilerDoesntSupportTarget: "The current compiler \'$1\' doesn't support the requested compilation target", + errInOutFlagNotExtern: "The `$1` modifier can be used only with imported types", errUser: "$1", warnCannotOpenFile: "cannot open \'$1\'", warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored", diff --git a/compiler/parser.nim b/compiler/parser.nim index e2d17b455..788ad7cd7 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -1520,6 +1520,13 @@ proc parseGenericParam(p: var TParser): PNode = # progress guaranteed while true: case p.tok.tokType + of tkIn, tkOut: + let kind = if p.tok.tokType == tkIn: nkInTy + else: nkOutTy + a = newNodeP(kind, p) + getTok(p) + expectIdent(p) + a.addSon(parseSymbol(p)) of tkSymbol, tkAccent: a = parseSymbol(p) if a.kind == nkEmpty: return @@ -1548,7 +1555,7 @@ proc parseGenericParamList(p: var TParser): PNode = getTok(p) optInd(p, result) # progress guaranteed - while p.tok.tokType in {tkSymbol, tkAccent}: + while p.tok.tokType in {tkSymbol, tkAccent, tkIn, tkOut}: var a = parseGenericParam(p) addSon(result, a) if p.tok.tokType notin {tkComma, tkSemiColon}: break diff --git a/compiler/pbraces.nim b/compiler/pbraces.nim index b59fbc6cf..fa4ccc139 100644 --- a/compiler/pbraces.nim +++ b/compiler/pbraces.nim @@ -1336,6 +1336,11 @@ proc parseGenericParam(p: var TParser): PNode = result = newNodeP(nkIdentDefs, p) while true: case p.tok.tokType + of tkIn, tkOut: + let t = p.tok.tokType + getTok(p) + expectIdent(p) + a = parseSymbol(p) of tkSymbol, tkAccent: a = parseSymbol(p) if a.kind == nkEmpty: return diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 1089ab7db..4fa4f7f32 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -429,7 +429,7 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode = for i in 1..sonsLen(n)-1: let formal = s.ast.sons[genericParamsPos].sons[i-1].typ let arg = n[i].typ - let tm = typeRel(m, formal, arg, true) + let tm = typeRel(m, formal, arg) if tm in {isNone, isConvertible}: return nil var newInst = generateInstance(c, s, m.bindings, n.info) newInst.typ.flags.excl tfUnresolved diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 8f2c802de..5da2b70fa 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -277,6 +277,10 @@ proc makeTypeFromExpr*(c: PContext, n: PNode): PType = assert n != nil result.n = n +proc newTypeWithSons2*(kind: TTypeKind, owner: PSym, sons: seq[PType]): PType = + result = newType(kind, owner) + result.sons = sons + proc newTypeWithSons*(c: PContext, kind: TTypeKind, sons: seq[PType]): PType = result = newType(kind, getCurrOwner(c)) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 8ff5fdd9c..59fa208d2 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -683,27 +683,9 @@ proc bracketedMacro(n: PNode): PSym = if result.kind notin {skMacro, skTemplate}: result = nil -proc semBracketedMacro(c: PContext; outer, inner: PNode; s: PSym; - flags: TExprFlags): PNode = - # We received untransformed bracket expression coming from macroOrTmpl[]. - # Transform it to macro or template call, where first come normal - # arguments, next come generic template arguments. - var sons = newSeq[PNode]() - sons.add inner.sons[0] - # Normal arguments: - for i in 1..<outer.len: - sons.add outer.sons[i] - # Generic template arguments from bracket expression: - for i in 1..<inner.len: - sons.add inner.sons[i] - shallowCopy(outer.sons, sons) - # FIXME: Shouldn't we check sfImmediate and call semDirectOp? - # However passing to semDirectOp doesn't work here. - case s.kind - of skMacro: result = semMacroExpr(c, outer, outer, s, flags) - of skTemplate: result = semTemplateExpr(c, outer, s, flags) - else: assert(false) - return +proc setGenericParams(c: PContext, n: PNode) = + for i in 1 .. <n.len: + n[i].typ = semTypeNode(c, n[i], nil) proc afterCallActions(c: PContext; n, orig: PNode, flags: TExprFlags): PNode = result = n @@ -745,7 +727,8 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode = elif n.sons[0].kind == nkBracketExpr: let s = bracketedMacro(n.sons[0]) if s != nil: - return semBracketedMacro(c, n, n.sons[0], s, flags) + setGenericParams(c, n[0]) + return semDirectOp(c, n, flags) let nOrig = n.copyTree semOpAux(c, n) @@ -2164,10 +2147,6 @@ proc shouldBeBracketExpr(n: PNode): bool = n.sons[0] = be return true -proc setGenericParams(c: PContext, n: PNode) = - for i in 1 .. <n.len: - n[i].typ = semTypeNode(c, n[i], nil) - proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = result = n if gCmd == cmdIdeTools: suggestExpr(c, n) diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 3e1989eaf..eb6259df0 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -89,9 +89,9 @@ proc semInstantiationInfo(c: PContext, n: PNode): PNode = proc toNode(t: PType, i: TLineInfo): PNode = result = newNodeIT(nkType, i, t) -const +const # these are types that use the bracket syntax for instantiation - # they can be subjected to the type traits `genericHead` and + # they can be subjected to the type traits `genericHead` and # `Uninstantiated` tyUserDefinedGenerics* = {tyGenericInst, tyGenericInvocation, tyUserTypeClassInst} @@ -109,27 +109,43 @@ proc uninstantiate(t: PType): PType = of tyCompositeTypeClass: uninstantiate t.sons[1] else: t -proc evalTypeTrait(trait: PNode, operand: PType, context: PSym): PNode = - var typ = operand.skipTypes({tyTypeDesc}) +proc evalTypeTrait(traitCall: PNode, operand: PType, context: PSym): PNode = + const skippedTypes = {tyTypeDesc} + let trait = traitCall[0] + internalAssert trait.kind == nkSym + var operand = operand.skipTypes(skippedTypes) + + template operand2: PType = + traitCall.sons[2].typ.skipTypes({tyTypeDesc}) + + template typeWithSonsResult(kind, sons): PNode = + newTypeWithSons2(kind, context, sons).toNode(traitCall.info) + case trait.sym.name.s + of "or", "|": + return typeWithSonsResult(tyOr, @[operand, operand2]) + of "and": + return typeWithSonsResult(tyAnd, @[operand, operand2]) + of "not": + return typeWithSonsResult(tyNot, @[operand]) of "name": - result = newStrNode(nkStrLit, typ.typeToString(preferName)) + result = newStrNode(nkStrLit, operand.typeToString(preferName)) result.typ = newType(tyString, context) - result.info = trait.info + result.info = traitCall.info of "arity": - result = newIntNode(nkIntLit, typ.len - ord(typ.kind==tyProc)) + result = newIntNode(nkIntLit, operand.len - ord(operand.kind==tyProc)) result.typ = newType(tyInt, context) - result.info = trait.info + result.info = traitCall.info of "genericHead": - var res = uninstantiate(typ) - if res == typ and res.kind notin tyMagicGenerics: - localError(trait.info, + var res = uninstantiate(operand) + if res == operand and res.kind notin tyMagicGenerics: + localError(traitCall.info, "genericHead expects a generic type. The given type was " & - typeToString(typ)) - return newType(tyError, context).toNode(trait.info) - result = res.base.toNode(trait.info) + typeToString(operand)) + return newType(tyError, context).toNode(traitCall.info) + result = res.base.toNode(traitCall.info) of "stripGenericParams": - result = uninstantiate(typ).toNode(trait.info) + result = uninstantiate(operand).toNode(traitCall.info) else: internalAssert false @@ -140,7 +156,7 @@ proc semTypeTraits(c: PContext, n: PNode): PNode = if t.sonsLen > 0: # This is either a type known to sem or a typedesc # param to a regular proc (again, known at instantiation) - result = evalTypeTrait(n[0], t, getCurrOwner(c)) + result = evalTypeTrait(n, t, getCurrOwner(c)) else: # a typedesc variable, pass unmodified to evals result = n diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index e52a1b5cc..f15cdddee 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -730,7 +730,9 @@ proc typeSectionLeftSidePass(c: PContext, n: PNode) = var s: PSym if name.kind == nkDotExpr: s = qualifiedLookUp(c, name, {checkUndeclared, checkModule}) - if s.kind != skType or s.typ.skipTypes(abstractPtrs).kind != tyObject or tfPartial notin s.typ.skipTypes(abstractPtrs).flags: + if s.kind != skType or + s.typ.skipTypes(abstractPtrs).kind != tyObject or + tfPartial notin s.typ.skipTypes(abstractPtrs).flags: localError(name.info, "only .partial objects can be extended") else: s = semIdentDef(c, name, skType) @@ -742,6 +744,87 @@ proc typeSectionLeftSidePass(c: PContext, n: PNode) = if sfGenSym notin s.flags: addInterfaceDecl(c, s) a.sons[0] = newSymNode(s) +proc checkCovariantParamsUsages(genericType: PType) = + var body = genericType{-1} + + proc traverseSubTypes(t: PType): bool = + template error(msg) = localError(genericType.sym.info, msg) + + result = false + + template subresult(r) = + let sub = r + result = result or sub + + case t.kind + of tyGenericParam: + t.sym.flags.incl sfWeakCovariant + return true + + of tyObject: + for field in t.n: + subresult traverseSubTypes(field.typ) + + of tyArray: + return traverseSubTypes(t[1]) + + of tyProc: + for subType in t.sons: + if subType != nil: + subresult traverseSubTypes(subType) + if result: + error("non-invariant type param used in a proc type: " & $t) + + of tySequence: + return traverseSubTypes(t[0]) + + of tyGenericInvocation: + let targetBody = t[0] + for i in 1 .. <t.len: + let param = t[i] + if param.kind == tyGenericParam: + if sfCovariant in param.sym.flags: + let formalFlags = targetBody[i-1].sym.flags + if sfCovariant notin formalFlags: + error("covariant param '" & param.sym.name.s & + "' used in a non-covariant position") + elif sfWeakCovariant in formalFlags: + param.sym.flags.incl sfWeakCovariant + result = true + elif sfContravariant in param.sym.flags: + let formalParam = targetBody[i-1].sym + if sfContravariant notin formalParam.flags: + error("contravariant param '" & param.sym.name.s & + "' used in a non-contravariant position") + result = true + else: + subresult traverseSubTypes(param) + + of tyAnd, tyOr, tyNot, tyStatic, tyBuiltInTypeClass, tyCompositeTypeClass: + error("non-invariant type parameters cannot be used with types such '" & $t & "'") + + of tyUserTypeClass, tyUserTypeClassInst: + error("non-invariant type parameters are not supported in concepts") + + of tyTuple: + for fieldType in t.sons: + subresult traverseSubTypes(fieldType) + + of tyPtr, tyRef, tyVar: + if t.base.kind == tyGenericParam: return true + return traverseSubTypes(t.base) + + of tyDistinct, tyAlias: + return traverseSubTypes(t.lastSon) + + of tyGenericInst: + internalAssert false + + else: + discard + + discard traverseSubTypes(body) + proc typeSectionRightSidePass(c: PContext, n: PNode) = for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] @@ -782,6 +865,22 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) = body.sym = s body.size = -1 # could not be computed properly s.typ.sons[sonsLen(s.typ) - 1] = body + if sfCovariant in s.flags: + checkCovariantParamsUsages(s.typ) + # XXX: This is a temporary limitation: + # The codegen currently produces various failures with + # generic imported types that have fields, but we need + # the fields specified in order to detect weak covariance. + # The proper solution is to teach the codegen how to handle + # such types, because this would offer various interesting + # possibilities such as instantiating C++ generic types with + # garbage collected Nim types. + if sfImportc in s.flags: + var body = s.typ.lastSon + if body.kind == tyObject: + # erases all declared fields + body.n.sons = nil + popOwner(c) closeScope(c) elif a.sons[2].kind != nkEmpty: diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 16066da91..e0dedf135 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1584,10 +1584,25 @@ proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode = # type for each generic param. the index # of the parameter will be stored in the # attached symbol. + var paramName = a.sons[j] + var covarianceFlag = sfPure + + if paramName.kind in {nkInTy, nkOutTy}: + if not nimEnableCovariance or paramName.kind == nkInTy: + if father == nil or sfImportc notin father.sym.flags: + localError(paramName.info, errInOutFlagNotExtern, + if paramName.kind == nkInTy: "in" else: "out") + covarianceFlag = if paramName.kind == nkInTy: sfContravariant + else: sfCovariant + if father != nil: father.sym.flags.incl sfCovariant + paramName = paramName[0] + var s = if finalType.kind == tyStatic or tfWildcard in typ.flags: - newSymG(skGenericParam, a.sons[j], c).linkTo(finalType) + newSymG(skGenericParam, paramName, c).linkTo(finalType) else: - newSymG(skType, a.sons[j], c).linkTo(finalType) + newSymG(skType, paramName, c).linkTo(finalType) + + if covarianceFlag != sfPure: s.flags.incl(covarianceFlag) if def.kind != nkEmpty: s.ast = def if father != nil: addSonSkipIntLit(father, s.typ) s.position = result.len diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index d6a0c6382..36975c3db 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -69,6 +69,12 @@ type mutabilityProblem*: uint8 # tyVar mismatch inheritancePenalty: int # to prefer closest father object type + TTypeRelFlag* = enum + trDontBind + trNoCovariance + + TTypeRelFlags* = set[TTypeRelFlag] + TTypeRelation* = enum # order is important! isNone, isConvertible, isIntConv, @@ -296,7 +302,9 @@ proc describeArgs*(c: PContext, n: PNode, startIdx = 1; add(result, argTypeToString(arg, prefer)) if i != sonsLen(n) - 1: add(result, ", ") -proc typeRel*(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation +proc typeRel*(c: var TCandidate, f, aOrig: PType, + flags: TTypeRelFlags = {}): TTypeRelation + proc concreteType(c: TCandidate, t: PType): PType = case t.kind of tyNil: @@ -860,7 +868,28 @@ template subtypeCheck() = if result <= isSubrange and f.lastSon.skipTypes(abstractInst).kind in {tyRef, tyPtr, tyVar}: result = isNone -proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = +proc isCovariantPtr(c: var TCandidate, f, a: PType): bool = + # this proc is always called for a pair of matching types + assert f.kind == a.kind + + template baseTypesCheck(lhs, rhs: PType): bool = + lhs.kind notin {tyPtr, tyRef, tyVar} and + typeRel(c, lhs, rhs, {trNoCovariance}) == isSubtype + + case f.kind + of tyRef, tyPtr: + return baseTypesCheck(f.base, a.base) + of tyGenericInst: + let body = f.base + return body == a.base and + a.sonsLen == 3 and + sfWeakCovariant notin body.sons[0].sym.flags and + baseTypesCheck(f.sons[1], a.sons[1]) + else: + return false + +proc typeRel(c: var TCandidate, f, aOrig: PType, + flags: TTypeRelFlags = {}): TTypeRelation = # typeRel can be used to establish various relationships between types: # # 1) When used with concrete types, it will check for type equivalence @@ -927,6 +956,8 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = result = isEqual return + template doBind: bool = trDontBind notin flags + # var and static arguments match regular modifier-free types var a = aOrig.skipTypes({tyStatic, tyVar}).maybeSkipDistinct(c.calleeSym) # XXX: Theoretically, maybeSkipDistinct could be called before we even @@ -956,23 +987,28 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = case a.kind of tyOr: + # XXX: deal with the current dual meaning of tyGenericParam + c.typedescMatched = true # seq[int|string] vs seq[number] # both int and string must match against number # but ensure that '[T: A|A]' matches as good as '[T: A]' (bug #2219): result = isGeneric for branch in a.sons: - let x = typeRel(c, f, branch, false) + let x = typeRel(c, f, branch, flags + {trDontBind}) if x == isNone: return isNone if x < result: result = x + return of tyAnd: + # XXX: deal with the current dual meaning of tyGenericParam + c.typedescMatched = true # seq[Sortable and Iterable] vs seq[Sortable] # only one match is enough for branch in a.sons: - let x = typeRel(c, f, branch, false) + let x = typeRel(c, f, branch, flags + {trDontBind}) if x != isNone: return if x >= isGeneric: isGeneric else: x - result = isNone + return isNone of tyNot: case f.kind @@ -996,7 +1032,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = of tyUserTypeClass, tyUserTypeClassInst: # consider this: 'var g: Node' *within* a concept where 'Node' # is a concept too (tgraph) - let x = typeRel(c, a, f, false) + let x = typeRel(c, a, f, flags + {trDontBind}) if x >= isGeneric: return isGeneric else: discard @@ -1042,7 +1078,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = of tyFloat128: result = handleFloatRange(f, a) of tyVar: if aOrig.kind == tyVar: result = typeRel(c, f.base, aOrig.base) - else: result = typeRel(c, f.base, aOrig) + else: result = typeRel(c, f.base, aOrig, flags + {trNoCovariance}) subtypeCheck() of tyArray: case a.kind @@ -1056,9 +1092,17 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = fRange = a else: fRange = prev - result = typeRel(c, f.sons[1].skipTypes({tyTypeDesc}), - a.sons[1].skipTypes({tyTypeDesc})) - if result < isGeneric: return isNone + let ff = f.sons[1].skipTypes({tyTypeDesc}) + let aa = a.sons[1].skipTypes({tyTypeDesc}) + result = typeRel(c, ff, aa) + if result < isGeneric: + if nimEnableCovariance and + trNoCovariance notin flags and + ff.kind == aa.kind and + isCovariantPtr(c, ff, aa): + result = isSubtype + else: + return isNone if fRange.rangeHasUnresolvedStatic: return inferStaticsInRange(c, fRange, a) @@ -1077,20 +1121,31 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = if tfOldSchoolExprStmt in f.sons[0].flags: if f.sons[0].kind == tyExpr: return elif f.sons[0].kind == tyStmt: return + + template matchArrayOrSeq(aBase: PType) = + let ff = f.base + let aa = aBase + let baseRel = typeRel(c, ff, aa) + if baseRel >= isGeneric: + result = isConvertible + elif nimEnableCovariance and + trNoCovariance notin flags and + ff.kind == aa.kind and + isCovariantPtr(c, ff, aa): + result = isConvertible + case a.kind of tyOpenArray, tyVarargs: result = typeRel(c, base(f), base(a)) if result < isGeneric: result = isNone of tyArray: if (f.sons[0].kind != tyGenericParam) and (a.sons[1].kind == tyEmpty): - result = isSubtype - elif typeRel(c, base(f), a.sons[1]) >= isGeneric: - result = isConvertible + return isSubtype + matchArrayOrSeq(a.sons[1]) of tySequence: if (f.sons[0].kind != tyGenericParam) and (a.sons[0].kind == tyEmpty): - result = isConvertible - elif typeRel(c, base(f), a.sons[0]) >= isGeneric: - result = isConvertible + return isConvertible + matchArrayOrSeq(a.sons[0]) of tyString: if f.kind == tyOpenArray: if f.sons[0].kind == tyChar: @@ -1105,8 +1160,17 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = if (f.sons[0].kind != tyGenericParam) and (a.sons[0].kind == tyEmpty): result = isSubtype else: - result = typeRel(c, f.sons[0], a.sons[0]) - if result < isGeneric: result = isNone + let ff = f.sons[0] + let aa = a.sons[0] + result = typeRel(c, ff, aa) + if result < isGeneric: + if nimEnableCovariance and + trNoCovariance notin flags and + ff.kind == aa.kind and + isCovariantPtr(c, ff, aa): + result = isSubtype + else: + result = isNone elif tfNotNil in f.flags and tfNotNil notin a.flags: result = isNilConversion of tyNil: result = f.allowsNil @@ -1158,7 +1222,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = if a.len < f.len: return isNone for i in 0..f.len-2: if typeRel(c, f.sons[i], a.sons[i]) == isNone: return isNone - result = typeRel(c, f.lastSon, a.lastSon) + result = typeRel(c, f.lastSon, a.lastSon, flags + {trNoCovariance}) subtypeCheck() if result <= isConvertible: result = isNone elif tfNotNil in f.flags and tfNotNil notin a.flags: @@ -1230,15 +1294,30 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = var m = c if a.kind == tyGenericInst: if roota.base == rootf.base: + let nextFlags = flags + {trNoCovariance} + var hasCovariance = false for i in 1 .. rootf.sonsLen-2: let ff = rootf.sons[i] let aa = roota.sons[i] - result = typeRel(c, ff, aa) - if result notin {isEqual, isGeneric}: return isNone - # if ff.kind == tyRange and result != isEqual: return isNone + result = typeRel(c, ff, aa, nextFlags) + if result notin {isEqual, isGeneric}: + if trNoCovariance notin flags and ff.kind == aa.kind: + let paramFlags = rootf.base.sons[i-1].sym.flags + hasCovariance = + if sfCovariant in paramFlags: + if sfWeakCovariant in paramFlags: + isCovariantPtr(c, ff, aa) + else: + ff.kind notin {tyRef, tyPtr} and result == isSubtype + else: + sfContravariant in paramFlags and + typeRel(c, aa, ff) == isSubtype + if hasCovariance: + continue + return isNone if prev == nil: put(c, f, a) - result = isGeneric + result = if hasCovariance: isGeneric else: isGeneric else: let fKind = rootf.lastSon.kind if fKind in {tyAnd, tyOr}: @@ -1450,7 +1529,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = result = isNone else: if f.sonsLen > 0 and f.sons[0].kind != tyNone: - result = typeRel(c, f.lastSon, a, false) + result = typeRel(c, f.lastSon, a, flags + {trDontBind}) if doBind and result notin {isNone, isGeneric}: let concrete = concreteType(c, a) if concrete == nil: return isNone diff --git a/compiler/vm.nim b/compiler/vm.nim index 043506e62..e201e98dc 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -1609,8 +1609,9 @@ proc setupMacroParam(x: PNode, typ: PType): TFullReg = iterator genericParamsInMacroCall*(macroSym: PSym, call: PNode): (PSym, PNode) = let gp = macroSym.ast[genericParamsPos] for i in 0 .. <gp.len: - let idx = macroSym.typ.len + i - yield (gp[i].sym, call.sons[idx]) + let genericParam = gp[i].sym + let posInCall = macroSym.typ.len + i + yield (genericParam, call[posInCall]) var evalMacroCounter: int diff --git a/doc/manual/type_rel.txt b/doc/manual/type_rel.txt index 5b68f73aa..1d1425934 100644 --- a/doc/manual/type_rel.txt +++ b/doc/manual/type_rel.txt @@ -111,6 +111,105 @@ relation is extended to the types ``var``, ``ref``, ``ptr``: .. XXX nil is a special value! +Covariance +---------- + +Covariance in Nim can be introduced only though pointer-like types such +as ``ptr`` and ``ref``. Sequence, Array and OpenArray types, instantiated +with pointer-like types will be considered covariant if and only if they +are also immutable. The introduction of a ``var`` modifier or additional +``ptr`` or ``ref`` indirections would result in invariant treatment of +these types. + +``proc`` types are currently always invariant, but future versions of Nim +may relax this rule. + +User-defined generic types may also be covariant with respect to some of +their parameters. By default, all generic params are considered invariant, +but you may choose the apply the prefix modifier ``in`` to a parameter to +make it contravariant or ``out`` to make it covariant: + +.. code-block:: nim + type + AnnotatedPtr[out T] = + metadata: MyTypeInfo + p: ref T + + RingBuffer[out T] = + startPos: int + data: seq[T] + + Action {.importcpp: "std::function<void ('0)>".} [in T] = object + +When the designated generic parameter is used to instantiate a pointer-like +type as in the case of `AnnotatedPtr` above, the resulting generic type will +also have pointer-like covariance: + +.. code-block:: nim + type + GuiWidget = object of RootObj + Button = object of GuiWidget + ComboBox = object of GuiWidget + + var + widgetPtr: AnnotatedPtr[GuiWidget] + buttonPtr: AnnotatedPtr[Button] + + ... + + proc drawWidget[T](x: AnnotatedPtr[GuiWidget]) = ... + + # you can call procs expecting base types by supplying a derived type + drawWidget(buttonPtr) + + # and you can convert more-specific pointer types to more general ones + widgetPtr = buttonPtr + +Just like with regular pointers, covariance will be enabled only for immutable +values: + +.. code-block:: nim + proc makeComboBox[T](x: var AnnotatedPtr[GuiWidget]) = + x.p = new(ComboBox) + + makeComboBox(buttonPtr) # Error, AnnotatedPtr[Button] cannot be modified + # to point to a ComboBox + +On the other hand, in the `RingBuffer` example above, the designated generic +param is used to instantiate the non-pointer ``seq`` type, which means that +the resulting generic type will have covariance that mimics an array or +sequence (i.e. it will be covariant only when instantiated with ``ptr`` and +``ref`` types): + +.. code-block:: nim + + type + Base = object of RootObj + Derived = object of Base + + proc consumeBaseValues(b: RingBuffer[Base]) = ... + + var derivedValues: RingBuffer[Derived] + + consumeBaseValues(derivedValues) # Error, Base and Derived values may differ + # in size + + proc consumeBasePointers(b: RingBuffer[ptr Base]) = ... + + var derivedPointers: RingBuffer[ptr Derived] + + consumeBaseValues(derivedPointers) # This is legal + +Please note that Nim will treat the user-defined pointer-like types as +proper alternatives to the built-in pointer types. That is, types such +as `seq[AnnotatedPtr[T]]` or `RingBuffer[AnnotatedPtr[T]]` will also be +considered covariant and you can create new pointer-like types by instantiating +other user-defined pointer-like types. + +The contravariant parameters introduced with the ``in`` modifier are currently +useful only when interfacing with imported types having such semantics. + + Convertible relation -------------------- A type ``a`` is **implicitly** convertible to type ``b`` iff the following @@ -119,6 +218,8 @@ algorithm returns true: .. code-block:: nim # XXX range types? proc isImplicitlyConvertible(a, b: PType): bool = + if isSubtype(a, b) or isCovariant(a, b): + return true case a.kind of int: result = b in {int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float, float32, float64} diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 83776f16b..5e0d4030d 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -68,6 +68,8 @@ type nnkProcTy, nnkIteratorTy, # iterator type nnkSharedTy, # 'shared T' + nnkInTy, + nnkOutTy, nnkEnumTy, nnkEnumFieldDef, nnkArglist, nnkPattern diff --git a/lib/system.nim b/lib/system.nim index 9b41253cc..05b4b88f2 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -49,6 +49,9 @@ type cstring* {.magic: Cstring.} ## built-in cstring (*compatible string*) type pointer* {.magic: Pointer.} ## built-in pointer type, use the ``addr`` ## operator to get a pointer to a variable + + typedesc* {.magic: TypeDesc.} ## meta type to denote a type description + const on* = true ## alias for ``true`` off* = false ## alias for ``false`` @@ -56,6 +59,15 @@ const {.push warning[GcMem]: off, warning[Uninit]: off.} {.push hints: off.} +proc `or` *(a, b: typedesc): typedesc {.magic: "TypeTrait", noSideEffect.} + ## Constructs an `or` meta class + +proc `and` *(a, b: typedesc): typedesc {.magic: "TypeTrait", noSideEffect.} + ## Constructs an `and` meta class + +proc `not` *(a: typedesc): typedesc {.magic: "TypeTrait", noSideEffect.} + ## Constructs an `not` meta class + type Ordinal* {.magic: Ordinal.}[T] ## Generic ordinal type. Includes integer, ## bool, character, and enumeration types @@ -66,11 +78,11 @@ type `ref`* {.magic: Pointer.}[T] ## built-in generic traced pointer type `nil` {.magic: "Nil".} + expr* {.magic: Expr, deprecated.} ## meta type to denote an expression (for templates) - ## **Deprecated** since version 0.15. Use ``untyped`` instead. + ## **Deprecated** since version 0.15. Use ``untyped`` instead. stmt* {.magic: Stmt, deprecated.} ## meta type to denote a statement (for templates) ## **Deprecated** since version 0.15. Use ``typed`` instead. - typedesc* {.magic: TypeDesc.} ## meta type to denote a type description void* {.magic: "VoidType".} ## meta type to denote the absence of any type auto* {.magic: Expr.} ## meta type for automatic type determination any* = distinct auto ## meta type for any supported type @@ -1332,6 +1344,7 @@ const hasThreadSupport = compileOption("threads") and not defined(nimscript) hasSharedHeap = defined(boehmgc) or defined(gogc) # don't share heaps; every thread has its own taintMode = compileOption("taintmode") + nimEnableCovariance* = defined(nimEnableCovariance) # or true when hasThreadSupport and defined(tcc) and not compileOption("tlsEmulation"): # tcc doesn't support TLS diff --git a/tests/cpp/tcovariancerules.nim b/tests/cpp/tcovariancerules.nim new file mode 100644 index 000000000..dfe4cb941 --- /dev/null +++ b/tests/cpp/tcovariancerules.nim @@ -0,0 +1,424 @@ +discard """ +cmd: "nim cpp $file" +output: ''' +cat +cat +dog +dog +cat +cat +dog +dog X +cat +cat +dog +dog +dog value +cat value +dog value +cat value +dog +dog +dog value +cat value +dog 1 +dog 2 +''' +""" + +template accept(x) = + static: assert(compiles(x)) + +template reject(x) = + static: assert(not compiles(x)) + +import macros + +macro skipElse(n: untyped): typed = n[0] + +template acceptWithCovariance(x, otherwise): typed = + when nimEnableCovariance: + x + else: + reject(x) + skipElse(otherwise) + +type + Animal = object of RootObj + x: string + + Dog = object of Animal + y: int + + Cat = object of Animal + z: int + + AnimalRef = ref Animal + AnimalPtr = ptr Animal + + RefAlias[T] = ref T + +var dog = new(Dog) +dog.x = "dog" + +var cat = new(Cat) +cat.x = "cat" + +proc makeDerivedRef(x: string): ref Dog = + new(result) + result.x = x + +proc makeDerived(x: string): Dog = + result.x = x + +var covariantSeq = @[makeDerivedRef("dog 1"), makeDerivedRef("dog 2")] +var nonCovariantSeq = @[makeDerived("dog 1"), makeDerived("dog 2")] +var covariantArr = [makeDerivedRef("dog 1"), makeDerivedRef("dog 2")] +var nonCovariantArr = [makeDerived("dog 1"), makeDerived("dog 2")] + +proc wantsCovariantSeq1(s: seq[ref Animal]) = + for a in s: echo a.x + +proc wantsCovariantSeq2(s: seq[AnimalRef]) = + for a in s: echo a.x + +proc wantsCovariantSeq3(s: seq[RefAlias[Animal]]) = + for a in s: echo a.x + +proc wantsCovariantOperArray(s: openarray[ref Animal]) = + for a in s: echo a.x + +proc modifiesCovariantOperArray(s: var openarray[ref Animal]) = + for a in s: echo a.x + +proc modifiesDerivedOperArray(s: var openarray[ref Dog]) = + for a in s: echo a.x + +proc wantsNonCovariantOperArray(s: openarray[Animal]) = + for a in s: echo a.x + +proc wantsCovariantArray(s: array[2, ref Animal]) = + for a in s: echo a.x + +proc wantsNonCovariantSeq(s: seq[Animal]) = + for a in s: echo a.x + +proc wantsNonCovariantArray(s: array[2, Animal]) = + for a in s: echo a.x + +proc modifiesCovariantSeq(s: var seq[ref Animal]) = + for a in s: echo a.x + +proc modifiesCovariantArray(s: var array[2, ref Animal]) = + for a in s: echo a.x + +proc modifiesCovariantSeq(s: ptr seq[ref Animal]) = + for a in s[]: echo a.x + +proc modifiesCovariantArray(s: ptr array[2, ref Animal]) = + for a in s[]: echo a.x + +proc modifiesDerivedSeq(s: var seq[ref Dog]) = + for a in s: echo a.x + +proc modifiesDerivedArray(s: var array[2, ref Dog]) = + for a in s: echo a.x + +proc modifiesDerivedSeq(s: ptr seq[ref Dog]) = + for a in s[]: echo a.x + +proc modifiesDerivedArray(s: ptr array[2, ref Dog]) = + for a in s[]: echo a.x + +accept: + wantsCovariantArray([AnimalRef(dog), AnimalRef(dog)]) + wantsCovariantArray([AnimalRef(cat), AnimalRef(dog)]) + wantsCovariantArray([AnimalRef(cat), dog]) + + # there is a special rule that detects the base + # type of polymorphic arrays + wantsCovariantArray([cat, dog]) + +acceptWithCovariance: + wantsCovariantArray([cat, cat]) +else: + echo "cat" + echo "cat" + +var animalRefArray: array[2, ref Animal] + +accept: + animalRefArray = [AnimalRef(dog), AnimalRef(dog)] + animalRefArray = [AnimalRef(cat), dog] + +acceptWithCovariance: + animalRefArray = [dog, dog] + wantsCovariantArray animalRefArray +else: + echo "dog" + echo "dog" + +accept: + var animal: AnimalRef = dog + animal = cat + +var vdog: Dog +vdog.x = "dog value" +var vcat: Cat +vcat.x = "cat value" + +reject: + vcat = vdog + +# XXX: The next two cases seem incosistent, perhaps we should change the rules +accept: + # truncating copies are allowed + var vanimal: Animal = vdog + vanimal = vdog + +reject: + # truncating copies are not allowed with arrays + var vanimalArray: array[2, Animal] + var vdogArray = [vdog, vdog] + vanimalArray = vdogArray + +accept: + # a more explicit version of a truncating copy that + # should probably always remain allowed + var vnextnimal: Animal = Animal(vdog) + +proc wantsRefSeq(x: seq[AnimalRef]) = discard + +accept: + wantsCovariantSeq1(@[AnimalRef(dog), AnimalRef(dog)]) + wantsCovariantSeq1(@[AnimalRef(cat), AnimalRef(dog)]) + wantsCovariantSeq1(@[AnimalRef(cat), dog]) + wantsCovariantSeq1(@[cat, dog]) + + wantsCovariantSeq2(@[AnimalRef(dog), AnimalRef(dog)]) + wantsCovariantSeq2(@[AnimalRef(cat), AnimalRef(dog)]) + wantsCovariantSeq2(@[AnimalRef(cat), dog]) + wantsCovariantSeq2(@[cat, dog]) + + wantsCovariantSeq3(@[AnimalRef(dog), AnimalRef(dog)]) + wantsCovariantSeq3(@[AnimalRef(cat), AnimalRef(dog)]) + wantsCovariantSeq3(@[AnimalRef(cat), dog]) + wantsCovariantSeq3(@[cat, dog]) + + wantsCovariantOperArray([cat, dog]) + +acceptWithCovariance: + wantsCovariantSeq1(@[cat, cat]) + wantsCovariantSeq2(@[dog, makeDerivedRef("dog X")]) + # XXX: wantsCovariantSeq3(@[cat, cat]) + + wantsCovariantOperArray(@[cat, cat]) + wantsCovariantOperArray([dog, dog]) +else: + echo "cat" + echo "cat" + echo "dog" + echo "dog X" + echo "cat" + echo "cat" + echo "dog" + echo "dog" + +var dogRefs = @[dog, dog] +var dogRefsArray = [dog, dog] +var animalRefs = @[dog, cat] + +accept: + modifiesDerivedArray(dogRefsArray) + modifiesDerivedSeq(dogRefs) + +reject modifiesCovariantSeq(dogRefs) +reject modifiesCovariantSeq(addr(dogRefs)) +reject modifiesCovariantSeq(dogRefs.addr) + +reject modifiesCovariantArray([dog, dog]) +reject modifiesCovariantArray(dogRefsArray) +reject modifiesCovariantArray(addr(dogRefsArray)) +reject modifiesCovariantArray(dogRefsArray.addr) + +var dogValues = @[vdog, vdog] +var dogValuesArray = [vdog, vdog] +var animalValues = @[Animal(vdog), Animal(vcat)] +var animalValuesArray = [Animal(vdog), Animal(vcat)] + +wantsNonCovariantSeq animalValues +wantsNonCovariantArray animalValuesArray + +reject wantsNonCovariantSeq(dogRefs) +reject modifiesCovariantOperArray(dogRefs) +reject wantsNonCovariantArray(dogRefsArray) +reject wantsNonCovariantSeq(dogValues) +reject wantsNonCovariantArray(dogValuesArray) +reject modifiesValueArray() + +modifiesDerivedOperArray dogRefs +reject modifiesDerivedOperArray(dogValues) +reject modifiesDerivedOperArray(animalRefs) + +wantsNonCovariantOperArray animalValues +reject wantsNonCovariantOperArray(animalRefs) +reject wantsNonCovariantOperArray(dogRefs) +reject wantsNonCovariantOperArray(dogValues) + +var animalRefSeq: seq[ref Animal] + +accept: + animalRefSeq = @[AnimalRef(dog), AnimalRef(dog)] + animalRefSeq = @[AnimalRef(cat), dog] + +acceptWithCovariance: + animalRefSeq = @[makeDerivedRef("dog 1"), makeDerivedRef("dog 2")] + wantsCovariantSeq1(animalRefSeq) +else: + echo "dog 1" + echo "dog 2" + +var pdog: ptr Dog +var pcat: ptr Cat + +proc wantsPointer(x: ptr Animal) = + discard + +accept: + wantsPointer pdog + wantsPointer pcat + +# covariance should be disabled when var is involved +proc wantsVarPointer1(x: var ptr Animal) = + discard + +proc wantsVarPointer2(x: var AnimalPtr) = + discard + +reject wantsVarPointer1(pdog) +reject wantsVarPointer2(pcat) + +# covariance may be allowed for certain extern types + +{.emit: """ +template <class T> struct FN { typedef void (*type)(T); }; +template <class T> struct ARR { typedef T DataType[2]; DataType data; }; +""".} + +type + MyPtr {.importcpp: "'0 *"} [out T] = object + + MySeq {.importcpp: "ARR<'0>", nodecl} [out T] = object + data: array[2, T] + + MyAction {.importcpp: "FN<'0>::type"} [in T] = object + +var + cAnimal: MyPtr[Animal] + cDog: MyPtr[Dog] + cCat: MyPtr[Cat] + + cAnimalFn: MyAction[Animal] + cCatFn: MyAction[Cat] + cDogFn: MyAction[Dog] + + cRefAnimalFn: MyAction[ref Animal] + cRefCatFn: MyAction[ref Cat] + cRefDogFn: MyAction[ref Dog] + +accept: + cAnimal = cDog + cAnimal = cCat + + cDogFn = cAnimalFn + cCatFn = cAnimalFn + + cRefDogFn = cRefAnimalFn + cRefCatFn = cRefAnimalFn + +reject: cDogFn = cRefAnimalFn +reject: cCatFn = cRefAnimalFn + +reject: cCat = cDog +reject: cAnimalFn = cDogFn +reject: cAnimalFn = cCatFn +reject: cRefAnimalFn = cRefDogFn +reject: cRefAnimalFn = cRefCatFn +reject: cRefAnimalFn = cDogFn + +var + ptrPtrDog: ptr ptr Dog + ptrPtrAnimal: ptr ptr Animal + +reject: ptrPtrDog = ptrPtrAnimal + +# Try to break the rules by introducing some tricky +# double indirection types: +var + cPtrRefAnimal: MyPtr[ref Animal] + cPtrRefDog: MyPtr[ref Dog] + + cPtrAliasRefAnimal: MyPtr[RefAlias[Animal]] + cPtrAliasRefDog: MyPtr[RefAlias[Dog]] + + cDoublePtrAnimal: MyPtr[MyPtr[Animal]] + cDoublePtrDog: MyPtr[MyPtr[Dog]] + +reject: cPtrRefAnimal = cPtrRefDog +reject: cDoublePtrAnimal = cDoublePtrDog +reject: cRefAliasPtrAnimal = cRefAliasPtrDog +reject: cPtrRefAnimal = cRefAliasPtrDog +reject: cPtrAliasRefAnimal = cPtrRefDog + +var + # Array and Sequence types are covariant only + # when instantiated with ref or ptr types: + cAnimals: MySeq[ref Animal] + cDogs: MySeq[ref Dog] + + # "User-defined" pointer types should be OK too: + cAnimalPtrSeq: MySeq[MyPtr[Animal]] + cDogPtrSeq: MySeq[MyPtr[Dog]] + + # Value types shouldn't work: + cAnimalValues: MySeq[Animal] + cDogValues: MySeq[Dog] + + # Double pointer types should not work either: + cAnimalRefPtrSeq: MySeq[ref MyPtr[Animal]] + cDogRefPtrSeq: MySeq[ref MyPtr[Dog]] + cAnimalPtrPtrSeq: MySeq[ptr ptr Animal] + cDogPtrPtrSeq: MySeq[ptr ptr Dog] + +accept: + cAnimals = cDogs + cAnimalPtrSeq = cDogPtrSeq + +reject: cAnimalValues = cDogValues +reject: cAnimalRefPtrSeq = cDogRefPtrSeq +reject: cAnimalPtrPtrSeq = cDogPtrPtrSeq + +proc wantsAnimalSeq(x: MySeq[Animal]) = discard +proc wantsAnimalRefSeq(x: MySeq[ref Animal]) = discard +proc modifiesAnimalRefSeq(x: var MySeq[ref Animal]) = discard +proc usesAddressOfAnimalRefSeq(x: ptr MySeq[ref Animal]) = discard + +accept wantsAnimalSeq(cAnimalValues) +reject wantsAnimalSeq(cDogValues) +reject wantsAnimalSeq(cAnimals) + +reject wantsAnimalRefSeq(cAnimalValues) +reject wantsAnimalRefSeq(cDogValues) +accept wantsAnimalRefSeq(cAnimals) +accept wantsAnimalRefSeq(cDogs) + +reject modifiesAnimalRefSeq(cAnimalValues) +reject modifiesAnimalRefSeq(cDogValues) +accept modifiesAnimalRefSeq(cAnimals) +reject modifiesAnimalRefSeq(cDogs) + +reject usesAddressOfAnimalRefSeq(addr cAnimalValues) +reject usesAddressOfAnimalRefSeq(addr cDogValues) +accept usesAddressOfAnimalRefSeq(addr cAnimals) +reject usesAddressOfAnimalRefSeq(addr cDogs) + diff --git a/tests/errmsgs/tinvalidinout.nim b/tests/errmsgs/tinvalidinout.nim new file mode 100644 index 000000000..ce7eb6022 --- /dev/null +++ b/tests/errmsgs/tinvalidinout.nim @@ -0,0 +1,26 @@ +discard """ +cmd: "nim check $file" +errormsg: "The `in` modifier can be used only with imported types" +nimout: ''' +tinvalidinout.nim(14, 7) Error: The `out` modifier can be used only with imported types +tinvalidinout.nim(17, 9) Error: The `in` modifier can be used only with imported types +tinvalidinout.nim(18, 9) Error: The `in` modifier can be used only with imported types +''' +""" + +type + Foo {.header: "foo.h", importcpp.} [in T] = object + + Bar[out X] = object + x: int + +proc f1[in T](x: T) = discard +proc f2[in T](x: T) {.importc: "f", header: "foo.h"} + +var + f: Foo[int] + b: Bar[string] + +f1 f +f2 b + diff --git a/tests/generics/tfakecovariance.nim b/tests/generics/tfakecovariance.nim new file mode 100644 index 000000000..0920cb504 --- /dev/null +++ b/tests/generics/tfakecovariance.nim @@ -0,0 +1,78 @@ +template accept(x) = + static: assert(compiles(x)) + +template reject(x) = + static: assert(not compiles(x)) + +type + BaseObj = object of RootObj + DerivedObj = object of BaseObj + NonDerivedObj = object + + Container[T] = object + +var base: BaseObj +var derived: DerivedObj +var nonDerived: NonDerivedObj + +var baseContainer: Container[BaseObj] +var derivedContainer: Container[DerivedObj] +var nonDerivedContainer: Container[NonDerivedObj] + +# We can fake covariance by listing some specific derived types that +# will be allowed with our overload. This is not a real covariance, +# because there will be multiple instantiations of the proc, but for +# many purposes, it will suffice: + +proc wantsSpecificContainers(c: Container[BaseObj or DerivedObj]) = discard + +accept wantsSpecificContainers(baseContainer) +accept wantsSpecificContainers(derivedContainer) + +reject wantsSpecificContainers(nonDerivedContainer) +reject wantsSpecificContainers(derived) + +# Now, let's make a more general solution able to catch all derived types: + +type + DerivedFrom[T] = concept type D + var derived: ref D + var base: ref T = derived + +proc wantsDerived(x: DerivedFrom[BaseObj]) = discard + +accept wantsDerived(base) +accept wantsDerived(derived) + +reject wantsDerived(nonDerived) +reject wantsDerived(baseContainer) + +proc wantsDerivedContainer(c: Container[DerivedFrom[BaseObj]]) = discard + +accept wantsDerivedContainer(baseContainer) +accept wantsDerivedContainer(derivedContainer) + +reject wantsDerivedContainer(nonDerivedContainer) + +# The previous solutions were solving the problem for a single overload. +# Let's solve it for multiple overloads by introducing a converter: + +type + OtherContainer[T] = object + +proc wantsBaseContainer1(c: OtherContainer[BaseObj]) = discard +proc wantsBaseContainer2(c: OtherContainer[BaseObj]) = discard + +converter derivedToBase(c: OtherContainer[DerivedFrom[BaseObj]]): OtherContainer[BaseObj] = discard + +block: + var baseContainer: OtherContainer[BaseObj] + var derivedContainer: OtherContainer[DerivedObj] + var nonDerivedContainer: OtherContainer[NonDerivedObj] + + accept wantsBaseContainer1(derivedContainer) + reject wantsBaseContainer1(nonDerivedContainer) + + accept wantsBaseContainer2(derivedContainer) + reject wantsBaseContainer2(nonDerivedContainer) + diff --git a/tests/generics/tgenericconst.nim b/tests/generics/tgenericconst.nim new file mode 100644 index 000000000..3c86888df --- /dev/null +++ b/tests/generics/tgenericconst.nim @@ -0,0 +1,39 @@ +discard """ +output: ''' +@[1, 2] +@[3, 4] +1 +''' +""" + +# https://github.com/nim-lang/Nim/issues/5756 + +type + Vec*[N : static[int]] = object + x: int + arr*: array[N, int32] + + Mat*[M,N: static[int]] = object + x: int + arr*: array[M, Vec[N]] + +proc vec2*(x,y:int32) : Vec[2] = + result.arr = [x,y] + result.x = 10 + +proc mat2*(a,b: Vec[2]): Mat[2,2] = + result.arr = [a,b] + result.x = 20 + +const M = mat2(vec2(1, 2), vec2(3, 4)) + +let m1 = M +echo @(m1.arr[0].arr) +echo @(m1.arr[1].arr) + +proc foo = + let m2 = M + echo m1.arr[0].arr[0] + +foo() + diff --git a/tests/generics/tptrinheritance.nim b/tests/generics/tptrinheritance.nim index 1e1115fa5..221b8777b 100644 --- a/tests/generics/tptrinheritance.nim +++ b/tests/generics/tptrinheritance.nim @@ -10,7 +10,7 @@ proc newMutableArrayAbstract*(): NSMutableArrayAbstract = discard template newMutableArray*(T: typedesc): NSMutableArray[T] = cast[NSMutableArray[T]](newMutableArrayAbstract()) -proc writeObjects*(p: NSPasteboard, o: ptr NSArray[NSPasteboardItem]) = discard +proc writeObjects*(p: NSPasteboard, o: NSArray[NSPasteboardItem]) = discard let a = newMutableArray NSPasteboardItem var x: NSMutableArray[NSPasteboardItem] diff --git a/tests/stdlib/tmarshal.nim b/tests/stdlib/tmarshal.nim index b05cb5a10..6a53a2964 100644 --- a/tests/stdlib/tmarshal.nim +++ b/tests/stdlib/tmarshal.nim @@ -1,7 +1,10 @@ discard """ output: '''{"age": 12, "bio": "\u042F Cletus", "blob": [65, 66, 67, 128], "name": "Cletus"} true -true''' +true +alpha 100 +omega 200 +''' """ import marshal @@ -83,3 +86,22 @@ var instance1 = Person(name: "Cletus", age: 12, echo($$instance1) echo(to[Person]($$instance1).bio == instance1.bio) echo(to[Person]($$instance1).blob == instance1.blob) + +# bug 5757 + +type + Something = object + x: string + y: int + +var data1 = """{"x": "alpha", "y": 100}""" +var data2 = """{"x": "omega", "y": 200}""" + +var r = to[Something](data1) + +echo r.x, " ", r.y + +r = to[Something](data2) + +echo r.x, " ", r.y + diff --git a/tests/template/tgenerictemplates.nim b/tests/template/tgenerictemplates.nim new file mode 100644 index 000000000..2c83bc0ec --- /dev/null +++ b/tests/template/tgenerictemplates.nim @@ -0,0 +1,13 @@ +type + SomeObj = object of RootObj + + Foo[T, U] = object + x: T + y: U + +template someTemplate[T](): tuple[id: int32, obj: T] = + var result: tuple[id: int32, obj: T] = (0'i32, T()) + result + +let ret = someTemplate[SomeObj]() + diff --git a/web/bountysource.nim b/web/bountysource.nim index 1d47cea56..5dfdb4497 100644 --- a/web/bountysource.nim +++ b/web/bountysource.nim @@ -33,13 +33,13 @@ proc getSupporters(self: BountySource): Future[JsonNode] {.async.} = let response = await self.client.get(apiUrl & "/supporters?order=monthly&per_page=200&team_slug=" & self.team) doAssert response.status.startsWith($Http200) - return parseJson(response.body) + return parseJson(await response.body) proc getGithubUser(username: string): Future[JsonNode] {.async.} = let client = newAsyncHttpClient() let response = await client.get(githubApiUrl & "/users/" & username) if response.status.startsWith($Http200): - return parseJson(response.body) + return parseJson(await response.body) else: echo("Could not get Github user: ", username, ". ", response.status) return nil |