diff --git a/compiler/ast.nim b/compiler/ast.nim index 5b68e9712b..ee63919195 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 f981f9595c..f2c6f076c2 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.. 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 e52a1b5cc7..f15cdddeeb 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 .. = 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 043506e624..e201e98dc6 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 .. ".} [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 83776f16ba..5e0d4030d8 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 9b41253cca..05b4b88f28 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 0000000000..dfe4cb941d --- /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 struct FN { typedef void (*type)(T); }; +template 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 0000000000..ce7eb6022c --- /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 0000000000..0920cb5040 --- /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 0000000000..3c86888df3 --- /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 1e1115fa56..221b8777ba 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 b05cb5a10a..6a53a29640 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 0000000000..2c83bc0ecb --- /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 1d47cea568..5dfdb44975 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