diff --git a/changelog.md b/changelog.md index 1310b6ae3f..faef6888f4 100644 --- a/changelog.md +++ b/changelog.md @@ -24,6 +24,40 @@ rounding guarantees (via the ## Language changes +- An experimental option `--experimental:typeBoundOps` has been added that + implements the RFC https://github.com/nim-lang/RFCs/issues/380. + This makes the behavior of interfaces like `hash`, `$`, `==` etc. more + reliable for nominal types across indirect/restricted imports. + + ```nim + # objs.nim + import std/hashes + + type + Obj* = object + x*, y*: int + z*: string # to be ignored for equality + + proc `==`*(a, b: Obj): bool = + a.x == b.x and a.y == b.y + + proc hash*(a: Obj): Hash = + $!(hash(a.x) &! hash(a.y)) + ``` + + ```nim + # main.nim + {.experimental: "typeBoundOps".} + from objs import Obj # objs.hash, objs.`==` not imported + import std/tables + + var t: Table[Obj, int] + t[Obj(x: 3, y: 4, z: "debug")] = 34 + echo t[Obj(x: 3, y: 4, z: "ignored")] # 34 + ``` + + See the [experimental manual](https://nim-lang.github.io/Nim/manual_experimental.html#typeminusbound-overloads) + for more information. ## Compiler changes diff --git a/compiler/options.nim b/compiler/options.nim index b77bdd2a33..d6f6727977 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -229,6 +229,7 @@ type # alternative to above: genericsOpenSym vtables + typeBoundOps LegacyFeature* = enum allowSemcheckedAstModification, diff --git a/compiler/semcall.nim b/compiler/semcall.nim index a195e9857f..fb2f9e97a7 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -69,6 +69,39 @@ proc initCandidateSymbols(c: PContext, headSymbol: PNode, result[0].scope, diagnostics) best.state = csNoMatch +proc isAttachableRoutineTo(prc: PSym, arg: PType): bool = + result = false + if arg.owner != prc.owner: return false + for i in 1 ..< prc.typ.len: + if prc.typ.n[i].kind == nkSym and prc.typ.n[i].sym.ast != nil: + # has default value, parameter is not considered in type attachment + continue + let t = nominalRoot(prc.typ[i]) + if t != nil and t.itemId == arg.itemId: + # parameter `i` is a nominal type in this module + # attachable if the nominal root `t` has the same id as `arg` + return true + +proc addTypeBoundSymbols(graph: ModuleGraph, arg: PType, name: PIdent, + filter: TSymKinds, marker: var IntSet, + syms: var seq[tuple[s: PSym, scope: int]]) = + # add type bound ops for `name` based on the argument type `arg` + if arg != nil: + # argument must be typed first, meaning arguments always + # matching `untyped` are ignored + let t = nominalRoot(arg) + if t != nil and t.owner.kind == skModule: + # search module for routines attachable to `t` + let module = t.owner + var iter = default(ModuleIter) + var s = initModuleIter(iter, graph, module, name) + while s != nil: + if s.kind in filter and s.isAttachableRoutineTo(t) and + not containsOrIncl(marker, s.id): + # least priority scope, less than explicit imports: + syms.add((s, -2)) + s = nextModuleIter(iter, graph) + proc pickBestCandidate(c: PContext, headSymbol: PNode, n, orig: PNode, initialBinding: PNode, @@ -88,10 +121,23 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, best, alt, o, diagnosticsFlag) if len(syms) == 0: return + let allowTypeBoundOps = typeBoundOps in c.features and + # qualified or bound symbols cannot refer to type bound ops + headSymbol.kind in {nkIdent, nkAccQuoted, nkOpenSymChoice, nkOpenSym} + var symMarker = initIntSet() + for s in syms: + symMarker.incl(s.s.id) # current overload being considered var sym = syms[0].s + let name = sym.name var scope = syms[0].scope + if allowTypeBoundOps: + for a in 1 ..< n.len: + # for every already typed argument, add type bound ops + let arg = n[a] + addTypeBoundSymbols(c.graph, arg.typ, name, filter, symMarker, syms) + # starts at 1 because 0 is already done with setup, only needs checking var nextSymIndex = 1 var z: TCandidate # current candidate @@ -106,6 +152,14 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, # may introduce new symbols with caveats described in recalc branch matches(c, n, orig, z) + if allowTypeBoundOps: + # this match may have given some arguments new types, + # in which case add their type bound ops as well + # type bound ops of arguments always matching `untyped` are not considered + for x in z.newlyTypedOperands: + let arg = n[x] + addTypeBoundSymbols(c.graph, arg.typ, name, filter, symMarker, syms) + if z.state == csMatch: # little hack so that iterators are preferred over everything else: if sym.kind == skIterator: @@ -136,7 +190,14 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, # before any further candidate init and compare. SLOW, but rare case. syms = initCandidateSymbols(c, headSymbol, initialBinding, filter, best, alt, o, diagnosticsFlag) - + symMarker = initIntSet() + for s in syms: + symMarker.incl(s.s.id) + if allowTypeBoundOps: + for a in 1 ..< n.len: + # for every already typed argument, add type bound ops + let arg = n[a] + addTypeBoundSymbols(c.graph, arg.typ, name, filter, symMarker, syms) # reset counter because syms may be in a new order symCount = c.currentScope.symbols.counter nextSymIndex = 0 diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 3538ea83cd..a1a1d21d7f 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -85,6 +85,9 @@ type inheritancePenalty: int firstMismatch*: MismatchInfo # mismatch info for better error messages diagnosticsEnabled*: bool + newlyTypedOperands*: seq[int] + ## indexes of arguments that are newly typechecked in this match + ## used for type bound op additions TTypeRelFlag* = enum trDontBind @@ -2728,7 +2731,7 @@ proc setSon(father: PNode, at: int, son: PNode) = # father[i] = newNodeIT(nkEmpty, son.info, getSysType(tyVoid)) # we are allowed to modify the calling node in the 'prepare*' procs: -proc prepareOperand(c: PContext; formal: PType; a: PNode): PNode = +proc prepareOperand(c: PContext; formal: PType; a: PNode, newlyTyped: var bool): PNode = if formal.kind == tyUntyped and formal.len != 1: # {tyTypeDesc, tyUntyped, tyTyped, tyError}: # a.typ == nil is valid @@ -2746,15 +2749,17 @@ proc prepareOperand(c: PContext; formal: PType; a: PNode): PNode = #elif formal.kind == tyTyped: {efDetermineType, efWantStmt} #else: {efDetermineType} result = c.semOperand(c, a, flags) + newlyTyped = true else: result = a considerGenSyms(c, result) if result.kind != nkHiddenDeref and result.typ.kind in {tyVar, tyLent} and c.matchedConcept == nil: result = newDeref(result) -proc prepareOperand(c: PContext; a: PNode): PNode = +proc prepareOperand(c: PContext; a: PNode, newlyTyped: var bool): PNode = if a.typ.isNil: result = c.semOperand(c, a, {efDetermineType}) + newlyTyped = true else: result = a considerGenSyms(c, result) @@ -2880,7 +2885,9 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int noMatch() m.baseTypeMatch = false m.typedescMatched = false - n[a][1] = prepareOperand(c, formal.typ, n[a][1]) + var newlyTyped = false + n[a][1] = prepareOperand(c, formal.typ, n[a][1], newlyTyped) + if newlyTyped: m.newlyTypedOperands.add(a) n[a].typ() = n[a][1].typ arg = paramTypesMatch(m, formal.typ, n[a].typ, n[a][1], n[a][1]) @@ -2904,7 +2911,9 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int if tfVarargs in m.callee.flags: # is ok... but don't increment any counters... # we have no formal here to snoop at: - n[a] = prepareOperand(c, n[a]) + var newlyTyped = false + n[a] = prepareOperand(c, n[a], newlyTyped) + if newlyTyped: m.newlyTypedOperands.add(a) if skipTypes(n[a].typ, abstractVar-{tyTypeDesc}).kind==tyString: m.call.add implicitConv(nkHiddenStdConv, getSysType(c.graph, n[a].info, tyCstring), @@ -2918,7 +2927,9 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int m.baseTypeMatch = false m.typedescMatched = false incl(marker, formal.position) - n[a] = prepareOperand(c, formal.typ, n[a]) + var newlyTyped = false + n[a] = prepareOperand(c, formal.typ, n[a], newlyTyped) + if newlyTyped: m.newlyTypedOperands.add(a) arg = paramTypesMatch(m, formal.typ, n[a].typ, n[a], nOrig[a]) if arg != nil and m.baseTypeMatch and container != nil: @@ -2954,7 +2965,9 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var Int else: m.baseTypeMatch = false m.typedescMatched = false - n[a] = prepareOperand(c, formal.typ, n[a]) + var newlyTyped = false + n[a] = prepareOperand(c, formal.typ, n[a], newlyTyped) + if newlyTyped: m.newlyTypedOperands.add(a) arg = paramTypesMatch(m, formal.typ, n[a].typ, n[a], nOrig[a]) if arg == nil: diff --git a/compiler/types.nim b/compiler/types.nim index fef564f586..8ac3ff52f7 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -1935,3 +1935,67 @@ proc isCharArrayPtr*(t: PType; allowPointerToChar: bool): bool = result = false else: result = false + +proc nominalRoot*(t: PType): PType = + ## the "name" type of a given instance of a nominal type, + ## i.e. the type directly associated with the symbol where the root + ## nominal type of `t` was defined, skipping things like generic instances, + ## aliases, `var`/`sink`/`typedesc` modifiers + ## + ## instead of returning the uninstantiated body of a generic type, + ## returns the type of the symbol instead (with tyGenericBody type) + result = nil + case t.kind + of tyAlias, tyVar, tySink: + # varargs? + result = nominalRoot(t.skipModifier) + of tyTypeDesc: + # for proc foo(_: type T) + result = nominalRoot(t.skipModifier) + of tyGenericInvocation, tyGenericInst: + result = t + # skip aliases, so this works in the same module but not in another module: + # type Foo[T] = object + # type Bar[T] = Foo[T] + # proc foo[T](x: Bar[T]) = ... # attached to type + while result.skipModifier.kind in {tyGenericInvocation, tyGenericInst}: + result = result.skipModifier + result = nominalRoot(result[0]) + of tyGenericBody: + result = t + # this time skip the aliases but take the generic body + while result.skipModifier.kind in {tyGenericInvocation, tyGenericInst}: + result = result.skipModifier[0] + let val = result.skipModifier + if val.kind in {tyDistinct, tyEnum, tyObject} or + (val.kind in {tyRef, tyPtr} and tfRefsAnonObj in val.flags): + # atomic nominal types, this generic body is attached to them + discard + else: + result = nominalRoot(val) + of tyCompositeTypeClass: + # parameter with type Foo + result = nominalRoot(t.skipModifier) + of tyGenericParam: + if t.genericParamHasConstraints: + # T: Foo + result = nominalRoot(t.genericConstraint) + else: + result = nil + of tyDistinct, tyEnum, tyObject: + result = t + of tyPtr, tyRef: + if tfRefsAnonObj in t.flags: + # in the case that we have `type Foo = ref object` etc + result = t + else: + # we could allow this in general, but there's things like `seq[Foo]` + #result = nominalRoot(t.skipModifier) + result = nil + of tyStatic: + # ? + result = nil + else: + # skips all typeclasses + # is this correct for `concept`? + result = nil diff --git a/doc/manual_experimental.md b/doc/manual_experimental.md index da51d59ad1..9bf64790f5 100644 --- a/doc/manual_experimental.md +++ b/doc/manual_experimental.md @@ -2667,3 +2667,114 @@ proc nothing() = ``` The current C(C++) backend implementation cannot generate code for gcc and for vcc at the same time. For example, `{.asmSyntax: "vcc".}` with the ICC compiler will not generate code with intel asm syntax, even though ICC can use both gcc-like and vcc-like asm. + +Type-bound overloads +==================== + +With the experimental option `--experimental:typeBoundOps`, each "root" +nominal type (namely `object`, `enum`, `distinct`, direct `Foo = ref object` +types as well as their generic versions) can have operations attached to it. +Exported top-level routines declared in the same scope as a nominal type +with a parameter having a type directly deriving from that nominal type (i.e. +with `var`/`sink`/`typedesc` modifiers or being in a generic constraint) +are considered "attached" to the respective nominal type. +This applies to every parameter regardless of placement. + +When a call to a symbol is openly overloaded and overload matching starts, +for all arguments in the call that have already undergone type checking, +routines with the same name attached to the root nominal type (if it exists) +of each given argument are added as a candidate to the overload match. +This also happens as arguments gradually get typed after every match to an overload. +This is so that the only overloads considered out of scope are +attached to the types of the given arguments, and that matches to +`untyped` or missing parameters are not influenced by outside overloads. + +If no overloads with a given name are in scope, then overload matching +will not begin, and so type-bound overloads are not considered for that name. +Similarly, if the only overloads with a given name require a parameter to be +`untyped` or missing, then type-bound overloads will not be considered for +the argument in that position. +Generally this means that a "base" overload with a compliant signature should +be in scope so that type-bound overloads can be used. + +In the case of ambiguity between distinct local/imported and type-bound symbols +in overload matching, type-bound symbols are considered as a less specific +scope than imports. + +An example with the `hash` interface in the standard library is as follows: + +```nim +# objs.nim +import std/hashes + +type + Obj* = object + x*, y*: int + z*: string # to be ignored for equality + +proc `==`*(a, b: Obj): bool = + a.x == b.x and a.y == b.y + +proc hash*(a: Obj): Hash = + $!(hash(a.x) &! hash(a.y)) + +# here both `==` and `hash` are attached to Obj +# 1. they are both exported +# 2. they are in the same scope as Obj +# 3. they have parameters with types directly deriving from Obj +# 4. Obj is nominal +``` + +```nim +# main.nim +{.experimental: "typeBoundOps".} +from objs import Obj # objs.hash, objs.`==` not imported +import std/tables +# tables use `hash`, only using the overloads in `std/hashes` and +# the ones in instantiation scope (in this case, there are none) + +var t: Table[Obj, int] +# because tables use `hash` and `==` in a compliant way, +# the overloads bound to Obj are also considered, and in this case match best +t[Obj(x: 3, y: 4, z: "debug")] = 34 +# if `hash` for all objects as in `std/hashes` was used, this would error: +echo t[Obj(x: 3, y: 4, z: "ignored")] # 34 +``` + +Another example, this time with `$` and indirect imports: + +```nim +# foo.nim +type Foo* = object + x*, y*: int + +proc `$`*(f: Foo): string = + "Foo(" & $f.x & ", " & $f.y & ")" +``` + +```nim +# bar.nim +import foo + +proc makeFoo*(x, y: int): Foo = + Foo(x: x, y: y) + +proc useFoo*(f: Foo) = + echo "used: ", f # directly calls `foo.$` from scope +``` + +```nim +# debugger.nim +proc debug*[T](obj: T) = + echo "debugging: ", obj # calls generic `$` +``` + +```nim +# main.nim +{.experimental: "typeBoundOps".} +import bar, debugger # `foo` not imported, so `foo.$` not in scope + +let f = makeFoo(123, 456) +useFoo(f) # used: Foo(123, 456) +debug(f) # debugging: Foo(123, 456) +``` diff --git a/tests/config.nims b/tests/config.nims index 1862bd40f2..11a4278035 100644 --- a/tests/config.nims +++ b/tests/config.nims @@ -43,3 +43,4 @@ when not defined(testsConciseTypeMismatch): switch("legacy", "verboseTypeMismatch") switch("experimental", "vtables") switch("experimental", "openSym") +switch("experimental", "typeBoundOps") diff --git a/tests/sandwich/config.nims b/tests/sandwich/config.nims new file mode 100644 index 0000000000..85506fdcad --- /dev/null +++ b/tests/sandwich/config.nims @@ -0,0 +1 @@ +switch("experimental", "typeBoundOps") diff --git a/tests/sandwich/mcontext_thread_local.nim b/tests/sandwich/mcontext_thread_local.nim new file mode 100644 index 0000000000..718888328d --- /dev/null +++ b/tests/sandwich/mcontext_thread_local.nim @@ -0,0 +1,15 @@ +# context_thread_local +import ./mtasks, ./mlistdeques + +export mlistdeques # Exporting the type with destructor doesn't help +# export tasks # solution 1. Exporting the inner type + +type MagicCompile = object + dq: ListDeque[Task] + +# var x: MagicCompile # solution 2. Instantiating the type with destructors +echo "Success" + +type + TLContext* = object + deque*: ListDeque[Task] diff --git a/tests/sandwich/mdollar1.nim b/tests/sandwich/mdollar1.nim new file mode 100644 index 0000000000..052bbf0a40 --- /dev/null +++ b/tests/sandwich/mdollar1.nim @@ -0,0 +1,5 @@ +type Foo* = object + x*, y*: int + +proc `$`*(f: Foo): string = + "Foo(" & $f.x & ", " & $f.y & ")" diff --git a/tests/sandwich/mdollar2.nim b/tests/sandwich/mdollar2.nim new file mode 100644 index 0000000000..97f2da18cb --- /dev/null +++ b/tests/sandwich/mdollar2.nim @@ -0,0 +1,7 @@ +import mdollar1 + +proc makeFoo*(x, y: int): Foo = + Foo(x: x, y: y) + +proc useFoo*(f: Foo) = + echo "used: ", f # directly calls `foo.$` from scope diff --git a/tests/sandwich/mdollar3.nim b/tests/sandwich/mdollar3.nim new file mode 100644 index 0000000000..e5e2e4744d --- /dev/null +++ b/tests/sandwich/mdollar3.nim @@ -0,0 +1,2 @@ +proc debug*[T](obj: T) = + echo "debugging: ", obj # calls generic `$` diff --git a/tests/sandwich/mfiles.nim b/tests/sandwich/mfiles.nim new file mode 100644 index 0000000000..fb36ecf5a9 --- /dev/null +++ b/tests/sandwich/mfiles.nim @@ -0,0 +1,11 @@ +import mhandles + +type + File* = ref object + handle: Handle[FD] + +proc close*[T: File](f: T) = + f.handle.close() + +proc newFile*(fd: FD): File = + File(handle: initHandle(FD -1)) diff --git a/tests/sandwich/mhandles.nim b/tests/sandwich/mhandles.nim new file mode 100644 index 0000000000..45b143e2c5 --- /dev/null +++ b/tests/sandwich/mhandles.nim @@ -0,0 +1,19 @@ +type + FD* = distinct cint + +type + AnyFD* = concept fd + close(fd) + +proc close*(fd: FD) = + discard + +type + Handle*[T: AnyFD] = object + fd: T + +proc close*[T: AnyFD](h: var Handle[T]) = + close h.fd + +proc initHandle*[T: AnyFD](fd: T): Handle[T] = + Handle[T](fd: fd) diff --git a/tests/sandwich/mitems.nim b/tests/sandwich/mitems.nim new file mode 100644 index 0000000000..ea9d7e7246 --- /dev/null +++ b/tests/sandwich/mitems.nim @@ -0,0 +1,52 @@ +import sets, hashes + +type + Fruit* = ref object + id*: int + + # Generic implementation. This doesn't work + EntGroup*[T] = ref object + freed*: HashSet[T] + +proc hash*(self: Fruit): Hash = hash(self.id) + +## +## VVV The Generic implementation. This doesn't work VVV +## + +proc initEntGroup*[T: Fruit](): EntGroup[T] = + result = EntGroup[T]() + result.freed = initHashSet[Fruit]() + var apple = Fruit(id: 20) + result.freed.incl(apple) + +proc get*[T: Fruit](fg: EntGroup[T]): T = + if len(fg.freed) == 0: return + # vvv It errors here + # type mismatch: ([1] fg.freed: HashSet[grouptest.Fruit]) + for it in fg.freed: + return it + +## +## VVV The Non-Generic implementation works VVV +## +type + # Non-generic implementation. This works. + FruitGroup* = ref object + freed*: HashSet[Fruit] + +proc initFruitGroup*(): FruitGroup = + result = FruitGroup() + result.freed = initHashSet[Fruit]() + var apple = Fruit(id: 20) + result.freed.incl(apple) + +proc getNoGeneric*(fg: FruitGroup): Fruit = + if len(fg.freed) == 0: return + for it in fg.freed: + return it + +proc `$`*(self: Fruit): string = + # For echo + if self == nil: return "Fruit()" + return "Fruit(" & $(self.id) & ")" diff --git a/tests/sandwich/mlistdeques.nim b/tests/sandwich/mlistdeques.nim new file mode 100644 index 0000000000..ae18ffaa42 --- /dev/null +++ b/tests/sandwich/mlistdeques.nim @@ -0,0 +1,35 @@ +# listdeques + +# needed, type bound ops aren't considered for undeclared procs +type Placeholder = object +proc allocate(_: Placeholder) = discard +proc delete(_: Placeholder) = discard + +type + StealableTask* = concept task, var mutTask, type T + task is ptr + task.prev is T + task.next is T + task.parent is T + task.fn is proc (param: pointer) {.nimcall.} + allocate(mutTask) + delete(task) + + ListDeque*[T: StealableTask] = object + head, tail: T + +func isEmpty*(dq: ListDeque): bool {.inline.} = + discard + +func popFirst*[T](dq: var ListDeque[T]): T = + discard + +proc `=destroy`*[T: StealableTask](dq: var ListDeque[T]) = + mixin delete + if dq.isEmpty(): + return + + while (let task = dq.popFirst(); not task.isNil): + delete(task) + + delete(dq.head) diff --git a/tests/sandwich/mobjhash.nim b/tests/sandwich/mobjhash.nim new file mode 100644 index 0000000000..ec342f5f69 --- /dev/null +++ b/tests/sandwich/mobjhash.nim @@ -0,0 +1,80 @@ +import hashes + +type + Obj* = object + x*, y*: int + z*: string # to be ignored for equality + +proc `==`*(a, b: Obj): bool = + a.x == b.x and a.y == b.y + +proc hash*(a: Obj): Hash = + !$(hash(a.x) !& hash(a.y)) + +type + RefObj* = ref object + x*, y*: int + z*: string # to be ignored for equality + +proc `==`*(a, b: RefObj): bool = + a.x == b.x and a.y == b.y + +proc hash*(a: RefObj): Hash = + !$(hash(a.x) !& hash(a.y)) + +type + GenericObj1*[T] = object + x*, y*: T + z*: string # to be ignored for equality + +proc `==`*[T](a, b: GenericObj1[T]): bool = + a.x == b.x and a.y == b.y + +proc hash*[T](a: GenericObj1[T]): Hash = + !$(hash(a.x) !& hash(a.y)) + +type + GenericObj2*[T] = object + x*, y*: T + z*: string # to be ignored for equality + +proc `==`*(a, b: GenericObj2): bool = + a.x == b.x and a.y == b.y + +proc hash*(a: GenericObj2): Hash = + !$(hash(a.x) !& hash(a.y)) + +type + GenericObj3*[T] = object + x*, y*: T + z*: string # to be ignored for equality + GenericObj3Alias*[T] = GenericObj3[T] + +proc `==`*[T](a, b: GenericObj3Alias[T]): bool = + a.x == b.x and a.y == b.y + +proc hash*[T](a: GenericObj3Alias[T]): Hash = + !$(hash(a.x) !& hash(a.y)) + +type + GenericObj4*[T] = object + x*, y*: T + z*: string # to be ignored for equality + GenericObj4Alias*[T] = GenericObj4[T] + +proc `==`*(a, b: GenericObj4): bool = + a.x == b.x and a.y == b.y + +proc hash*(a: GenericObj4): Hash = + !$(hash(a.x) !& hash(a.y)) + +type + GenericRefObj*[T] = ref object + x*, y*: T + z*: string # to be ignored for equality + +proc `==`*[T](a, b: GenericRefObj[T]): bool = + a.x == b.x and a.y == b.y + +proc hash*[T](a: GenericRefObj[T]): Hash = + !$(hash(a.x) !& hash(a.y)) diff --git a/tests/sandwich/mqueuecontainer.nim b/tests/sandwich/mqueuecontainer.nim new file mode 100644 index 0000000000..3ddbc5a3a9 --- /dev/null +++ b/tests/sandwich/mqueuecontainer.nim @@ -0,0 +1,13 @@ +# original example used queues +import deques + +type + QueueContainer*[T] = object + q: ref Deque[T] + +proc init*[T](c: var QueueContainer[T]) = + new(c.q) + c.q[] = initDeque[T](64) + +proc addToQ*[T](c: var QueueContainer[T], item: T) = + c.q[].addLast(item) diff --git a/tests/sandwich/msetin.nim b/tests/sandwich/msetin.nim new file mode 100644 index 0000000000..7aa44df6c5 --- /dev/null +++ b/tests/sandwich/msetin.nim @@ -0,0 +1,10 @@ +import std/sets +template foo*[T](a: T) = +# proc foo*[T](a: T) = # works + var s: HashSet[T] + # echo contains(s, a) # works + let x = a in s # BUG + doAssert not x + doAssert not (a in s) + doAssert a notin s +when isMainModule: foo(1) # works diff --git a/tests/sandwich/msetiter1.nim b/tests/sandwich/msetiter1.nim new file mode 100644 index 0000000000..8049dcf9c8 --- /dev/null +++ b/tests/sandwich/msetiter1.nim @@ -0,0 +1,7 @@ +import sets + +proc initH*[V]: HashSet[V] = + result = initHashSet[V]() + +proc foo*[V](h: var HashSet[V], c: seq[V]) = + h = h + c.toHashSet() diff --git a/tests/sandwich/msetiter2.nim b/tests/sandwich/msetiter2.nim new file mode 100644 index 0000000000..b78175b310 --- /dev/null +++ b/tests/sandwich/msetiter2.nim @@ -0,0 +1,4 @@ +import sets, sequtils + +proc dedupe*[T](arr: openArray[T]): seq[T] = + arr.toHashSet.toSeq diff --git a/tests/sandwich/mtasks.nim b/tests/sandwich/mtasks.nim new file mode 100644 index 0000000000..f585f4f7b4 --- /dev/null +++ b/tests/sandwich/mtasks.nim @@ -0,0 +1,14 @@ +# tasks.nim +type + Task* = ptr object + parent*: Task + prev*: Task + next*: Task + fn*: proc (param: pointer) {.nimcall.} + +# StealableTask API +proc allocate*(task: var Task) = + discard + +proc delete*(task: Task) = + discard diff --git a/tests/sandwich/tcontext_thread_local.nim b/tests/sandwich/tcontext_thread_local.nim new file mode 100644 index 0000000000..f015014b8e --- /dev/null +++ b/tests/sandwich/tcontext_thread_local.nim @@ -0,0 +1,12 @@ +discard """ + output: ''' +Success +''' +""" + +# modified issue #12620, see placeholder procs in mlistdeques + +# runtime.nim +import ./mcontext_thread_local + +var localCtx* : TLContext diff --git a/tests/sandwich/tdollar.nim b/tests/sandwich/tdollar.nim new file mode 100644 index 0000000000..76cc0723bf --- /dev/null +++ b/tests/sandwich/tdollar.nim @@ -0,0 +1,12 @@ +discard """ + output: ''' +used: Foo(123, 456) +debugging: Foo(123, 456) +''' +""" + +import mdollar2, mdollar3 # `mdollar1` not imported, so `mdollar1.$` not in scope + +let f = makeFoo(123, 456) +useFoo(f) # used: Foo(123, 456) +debug(f) # debugging: Foo(123, 456) diff --git a/tests/sandwich/tfilehandles.nim b/tests/sandwich/tfilehandles.nim new file mode 100644 index 0000000000..3bed18dfae --- /dev/null +++ b/tests/sandwich/tfilehandles.nim @@ -0,0 +1,8 @@ +# issue #16755 + +import mfiles +from mhandles import FD +#import handles <- do this and it works + +let wr = newFile(FD -1) +close wr diff --git a/tests/sandwich/titems.nim b/tests/sandwich/titems.nim new file mode 100644 index 0000000000..0c3b41026a --- /dev/null +++ b/tests/sandwich/titems.nim @@ -0,0 +1,13 @@ +# issue #22984 +# import sets # <<-- Uncomment this to make the error go away + +import mitems + +## The generic implementation +var grp: EntGroup[Fruit] = initEntGroup[Fruit]() +doAssert $get(grp) == "Fruit(20)" ## Errors here + + +## This works though (Non-generic) +var fruitGroup: FruitGroup = initFruitGroup() +doAssert $getNoGeneric(fruitGroup) == "Fruit(20)" diff --git a/tests/sandwich/tobjhash.nim b/tests/sandwich/tobjhash.nim new file mode 100644 index 0000000000..6c82407e77 --- /dev/null +++ b/tests/sandwich/tobjhash.nim @@ -0,0 +1,59 @@ +# https://github.com/nim-lang/RFCs/issues/380 + +from mobjhash import Obj, RefObj, GenericObj1, GenericObj2, GenericObj3, GenericObj4, GenericRefObj +import tables + +block: + var t: Table[Obj, int] + t[Obj(x: 3, y: 4, z: "debug")] = 34 + doAssert t[Obj(x: 3, y: 4, z: "ignored")] == 34 + doAssert Obj(x: 4, y: 3, z: "debug") notin t + +block: + var t: Table[RefObj, int] + t[RefObj(x: 3, y: 4, z: "debug")] = 34 + doAssert t[RefObj(x: 3, y: 4, z: "ignored")] == 34 + doAssert RefObj(x: 4, y: 3, z: "debug") notin t + +block: + var t: Table[GenericObj1[float], int] + t[GenericObj1[float](x: 3, y: 4, z: "debug")] = 34 + doAssert t[GenericObj1[float](x: 3, y: 4, z: "ignored")] == 34 + doAssert GenericObj1[float](x: 4, y: 3, z: "debug") notin t + +block: + var t: Table[GenericObj1[int], int] + t[GenericObj1[int](x: 3, y: 4, z: "debug")] = 34 + doAssert t[GenericObj1[int](x: 3, y: 4, z: "ignored")] == 34 + doAssert GenericObj1[int](x: 4, y: 3, z: "debug") notin t + +block: + var t: Table[GenericObj2[float], int] + t[GenericObj2[float](x: 3, y: 4, z: "debug")] = 34 + doAssert t[GenericObj2[float](x: 3, y: 4, z: "ignored")] == 34 + doAssert GenericObj2[float](x: 4, y: 3, z: "debug") notin t + +block: + var t: Table[GenericObj3[float], int] + t[GenericObj3[float](x: 3, y: 4, z: "debug")] = 34 + doAssert t[GenericObj3[float](x: 3, y: 4, z: "ignored")] == 34 + doAssert GenericObj3[float](x: 4, y: 3, z: "debug") notin t + +block: + var t: Table[GenericObj4[float], int] + t[GenericObj4[float](x: 3, y: 4, z: "debug")] = 34 + doAssert t[GenericObj4[float](x: 3, y: 4, z: "ignored")] == 34 + doAssert GenericObj4[float](x: 4, y: 3, z: "debug") notin t + +block: + var t: Table[GenericRefObj[float], int] + t[GenericRefObj[float](x: 3, y: 4, z: "debug")] = 34 + doAssert t[GenericRefObj[float](x: 3, y: 4, z: "ignored")] == 34 + doAssert GenericRefObj[float](x: 4, y: 3, z: "debug") notin t + +block: + type LocalAlias[T] = GenericObj4[T] + var t: Table[LocalAlias[float], int] + t[LocalAlias[float](x: 3, y: 4, z: "debug")] = 34 + doAssert t[LocalAlias[float](x: 3, y: 4, z: "ignored")] == 34 + doAssert LocalAlias[float](x: 4, y: 3, z: "debug") notin t diff --git a/tests/sandwich/tqueuecontainer.nim b/tests/sandwich/tqueuecontainer.nim new file mode 100644 index 0000000000..863e44cb74 --- /dev/null +++ b/tests/sandwich/tqueuecontainer.nim @@ -0,0 +1,10 @@ +# issue #4773 + +import mqueuecontainer + +# works if this is uncommented (or if the `queuecontainer` exports `queues`): +# import queues + +var c: QueueContainer[int] +c.init() +c.addToQ(1) diff --git a/tests/sandwich/tsethash.nim b/tests/sandwich/tsethash.nim new file mode 100644 index 0000000000..ad244d1e66 --- /dev/null +++ b/tests/sandwich/tsethash.nim @@ -0,0 +1,24 @@ +# issue #14729 + +import sets, hashes + +type + Iterable[T] = concept x + for value in items(x): + type(value) is T + + Foo[T] = object + t: T + +proc myToSet[T](keys: Iterable[T]): HashSet[T] = + for x in items(keys): result.incl(x) + +proc hash[T](foo: Foo[T]): Hash = + echo "specific hash" + +proc `==`[T](lhs, rhs: Foo[T]): bool = + echo "specific equals" + +let + f = Foo[string](t: "test") + hs = [f, f].myToSet() diff --git a/tests/sandwich/tsetin.nim b/tests/sandwich/tsetin.nim new file mode 100644 index 0000000000..1dd3d2bd9e --- /dev/null +++ b/tests/sandwich/tsetin.nim @@ -0,0 +1,4 @@ +# issue #18150 + +import msetin +foo(1) diff --git a/tests/sandwich/tsetiter1.nim b/tests/sandwich/tsetiter1.nim new file mode 100644 index 0000000000..60616c4968 --- /dev/null +++ b/tests/sandwich/tsetiter1.nim @@ -0,0 +1,17 @@ +# comment on issue #11167 + +import hashes + +import msetiter1 + +type + Choice = object + i: int + +proc hash(c: Choice): Hash = + result = Hash(c.i) + +var h = initH[Choice]() +let c = @[Choice(i: 1)] + +foo(h, c) diff --git a/tests/sandwich/tsetiter2.nim b/tests/sandwich/tsetiter2.nim new file mode 100644 index 0000000000..f89eac075f --- /dev/null +++ b/tests/sandwich/tsetiter2.nim @@ -0,0 +1,9 @@ +# comment on issue #11167 + +import msetiter2 + +let x = dedupe([1, 2, 3]) +doAssert x.len == 3 +doAssert 1 in x +doAssert 2 in x +doAssert 3 in x