mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-02 03:02:31 +00:00
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:
34
changelog.md
34
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
|
||||
|
||||
|
||||
@@ -229,6 +229,7 @@ type
|
||||
# alternative to above:
|
||||
genericsOpenSym
|
||||
vtables
|
||||
typeBoundOps
|
||||
|
||||
LegacyFeature* = enum
|
||||
allowSemcheckedAstModification,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
@@ -43,3 +43,4 @@ when not defined(testsConciseTypeMismatch):
|
||||
switch("legacy", "verboseTypeMismatch")
|
||||
switch("experimental", "vtables")
|
||||
switch("experimental", "openSym")
|
||||
switch("experimental", "typeBoundOps")
|
||||
|
||||
1
tests/sandwich/config.nims
Normal file
1
tests/sandwich/config.nims
Normal file
@@ -0,0 +1 @@
|
||||
switch("experimental", "typeBoundOps")
|
||||
15
tests/sandwich/mcontext_thread_local.nim
Normal file
15
tests/sandwich/mcontext_thread_local.nim
Normal 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]
|
||||
5
tests/sandwich/mdollar1.nim
Normal file
5
tests/sandwich/mdollar1.nim
Normal file
@@ -0,0 +1,5 @@
|
||||
type Foo* = object
|
||||
x*, y*: int
|
||||
|
||||
proc `$`*(f: Foo): string =
|
||||
"Foo(" & $f.x & ", " & $f.y & ")"
|
||||
7
tests/sandwich/mdollar2.nim
Normal file
7
tests/sandwich/mdollar2.nim
Normal 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
|
||||
2
tests/sandwich/mdollar3.nim
Normal file
2
tests/sandwich/mdollar3.nim
Normal file
@@ -0,0 +1,2 @@
|
||||
proc debug*[T](obj: T) =
|
||||
echo "debugging: ", obj # calls generic `$`
|
||||
11
tests/sandwich/mfiles.nim
Normal file
11
tests/sandwich/mfiles.nim
Normal 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))
|
||||
19
tests/sandwich/mhandles.nim
Normal file
19
tests/sandwich/mhandles.nim
Normal 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
52
tests/sandwich/mitems.nim
Normal 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) & ")"
|
||||
35
tests/sandwich/mlistdeques.nim
Normal file
35
tests/sandwich/mlistdeques.nim
Normal 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)
|
||||
80
tests/sandwich/mobjhash.nim
Normal file
80
tests/sandwich/mobjhash.nim
Normal 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))
|
||||
13
tests/sandwich/mqueuecontainer.nim
Normal file
13
tests/sandwich/mqueuecontainer.nim
Normal 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
10
tests/sandwich/msetin.nim
Normal 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
|
||||
7
tests/sandwich/msetiter1.nim
Normal file
7
tests/sandwich/msetiter1.nim
Normal 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()
|
||||
4
tests/sandwich/msetiter2.nim
Normal file
4
tests/sandwich/msetiter2.nim
Normal 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
14
tests/sandwich/mtasks.nim
Normal 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
|
||||
12
tests/sandwich/tcontext_thread_local.nim
Normal file
12
tests/sandwich/tcontext_thread_local.nim
Normal 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
|
||||
12
tests/sandwich/tdollar.nim
Normal file
12
tests/sandwich/tdollar.nim
Normal 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)
|
||||
8
tests/sandwich/tfilehandles.nim
Normal file
8
tests/sandwich/tfilehandles.nim
Normal 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
13
tests/sandwich/titems.nim
Normal 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)"
|
||||
59
tests/sandwich/tobjhash.nim
Normal file
59
tests/sandwich/tobjhash.nim
Normal 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
|
||||
10
tests/sandwich/tqueuecontainer.nim
Normal file
10
tests/sandwich/tqueuecontainer.nim
Normal 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)
|
||||
24
tests/sandwich/tsethash.nim
Normal file
24
tests/sandwich/tsethash.nim
Normal 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()
|
||||
4
tests/sandwich/tsetin.nim
Normal file
4
tests/sandwich/tsetin.nim
Normal file
@@ -0,0 +1,4 @@
|
||||
# issue #18150
|
||||
|
||||
import msetin
|
||||
foo(1)
|
||||
17
tests/sandwich/tsetiter1.nim
Normal file
17
tests/sandwich/tsetiter1.nim
Normal 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)
|
||||
9
tests/sandwich/tsetiter2.nim
Normal file
9
tests/sandwich/tsetiter2.nim
Normal 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
|
||||
Reference in New Issue
Block a user