implement type bound operation RFC (#24315)

closes https://github.com/nim-lang/RFCs/issues/380, fixes #4773, fixes
#14729, fixes #16755, fixes #18150, fixes #22984, refs #11167 (only some
comments fixed), refs #12620 (needs tiny workaround)

The compiler gains a concept of root "nominal" types (i.e. objects,
enums, distincts, direct `Foo = ref object`s, generic versions of all of
these). Exported top-level routines in the same module as the nominal
types that their parameter types derive from (i.e. with
`var`/`sink`/`typedesc`/generic constraints) are considered attached to
the respective type, as the RFC states. This happens for every argument
regardless of placement.

When a call is overloaded and overload matching starts, for all
arguments in the call that already have a type, we add any operation
with the same name in the scope of the root nominal type of each
argument (if it exists) to the overload match. This also happens as
arguments gradually get typed after every overload match. This restricts
the considered overloads to ones attached to the given arguments, as
well as preventing `untyped` arguments from being forcefully typed due
to unrelated overloads. There are some caveats:

* If no overloads with a name are in scope, type bound ops are not
triggered, i.e. if `foo` is not declared, `foo(x)` will not consider a
type bound op for `x`.
* If overloads in scope do not have enough parameters up to the argument
which needs its type bound op considered, then type bound ops are also
not added. For example, if only `foo()` is in scope, `foo(x)` will not
consider a type bound op for `x`.

In the cases of "generic interfaces" like `hash`, `$`, `items` etc. this
is not really a problem since any code using it will have at least one
typed overload imported. For arbitrary versions of these though, as in
the test case for #12620, a workaround is to declare a temporary
"template" overload that never matches:

```nim
# neither have to be exported, just needed for any use of `foo`:
type Placeholder = object
proc foo(_: Placeholder) = discard
```

I don't know what a "proper" version of this could be, maybe something
to do with the new concepts.

Possible directions:

A limitation with the proposal is that parameters like `a: ref Foo` are
not attached to any type, even if `Foo` is nominal. Fixing this for just
`ptr`/`ref` would be a special case, parameters like `seq[Foo]` would
still not be attached to `Foo`. We could also skip any *structural* type
but this could produce more than one nominal type, i.e. `(Foo, Bar)`
(not that this is hard to implement, it just might be unexpected).

Converters do not use type bound ops, they still need to be in scope to
implicitly convert. But maybe they could also participate in the nominal
type consideration: if `Generic[T] = distinct T` has a converter to `T`,
both `Generic` and `T` can be considered as nominal roots.

The other restriction in the proposal, being in the same scope as the
nominal type, could maybe be worked around by explicitly attaching to
the type, i.e.: `proc foo(x: T) {.attach: T.}`, similar to class
extensions in newer OOP languages. The given type `T` needs to be
obtainable from the type of the given argument `x` however, i.e.
something like `proc foo(x: ref T) {.attach: T.}` doesn't work to fix
the `ref` issue since the compiler never obtains `T` from a given `ref
T` argument. Edit: Since the module is queried now, this is likely not
possible.

---------

Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
(cherry picked from commit 2864830941)
This commit is contained in:
metagn
2024-10-25 12:26:42 +03:00
committed by narimiran
parent 850132d37c
commit ac8c44e08d
32 changed files with 735 additions and 7 deletions

View File

@@ -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

View File

@@ -229,6 +229,7 @@ type
# alternative to above:
genericsOpenSym
vtables
typeBoundOps
LegacyFeature* = enum
allowSemcheckedAstModification,

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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)
```

View File

@@ -43,3 +43,4 @@ when not defined(testsConciseTypeMismatch):
switch("legacy", "verboseTypeMismatch")
switch("experimental", "vtables")
switch("experimental", "openSym")
switch("experimental", "typeBoundOps")

View File

@@ -0,0 +1 @@
switch("experimental", "typeBoundOps")

View File

@@ -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]

View File

@@ -0,0 +1,5 @@
type Foo* = object
x*, y*: int
proc `$`*(f: Foo): string =
"Foo(" & $f.x & ", " & $f.y & ")"

View File

@@ -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

View File

@@ -0,0 +1,2 @@
proc debug*[T](obj: T) =
echo "debugging: ", obj # calls generic `$`

11
tests/sandwich/mfiles.nim Normal file
View File

@@ -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))

View File

@@ -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)

52
tests/sandwich/mitems.nim Normal file
View File

@@ -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) & ")"

View File

@@ -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)

View File

@@ -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))

View File

@@ -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)

10
tests/sandwich/msetin.nim Normal file
View File

@@ -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

View File

@@ -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()

View File

@@ -0,0 +1,4 @@
import sets, sequtils
proc dedupe*[T](arr: openArray[T]): seq[T] =
arr.toHashSet.toSeq

14
tests/sandwich/mtasks.nim Normal file
View File

@@ -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

View File

@@ -0,0 +1,12 @@
discard """
output: '''
Success
'''
"""
# modified issue #12620, see placeholder procs in mlistdeques
# runtime.nim
import ./mcontext_thread_local
var localCtx* : TLContext

View File

@@ -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)

View File

@@ -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

13
tests/sandwich/titems.nim Normal file
View File

@@ -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)"

View File

@@ -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

View File

@@ -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)

View File

@@ -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()

View File

@@ -0,0 +1,4 @@
# issue #18150
import msetin
foo(1)

View File

@@ -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)

View File

@@ -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