-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix #8519 T.distinctBase to reverse T = distinct A #8531
Conversation
UsageI'd like something to undistinct variables as well, current usage would require: var x: distinct seq[seq[int]] # replace by a complex type
(x.type.undistinct)(x) for conversion Simple TestsThere should be a test case with generics and static type. I checked that generics work: import typetraits
type
Foo[T] = distinct seq[T]
var a: Foo[int]
doAssert undistinct(type a) is seq[int] Stress testUnfortunately as-is undistinct doesn't pass my "type system stress test" which combines macro, static and generics. Super complex recursive big int type taken from Stint. import macros, typetraits
macro uintImpl*(bits: static[int]): untyped =
# Release version, StUint[64] = uint64.
assert (bits and (bits-1)) == 0, $bits & " is not a power of 2"
assert bits >= 8, "The number of bits in a should be greater or equal to 8"
if bits >= 128:
let inner = getAST(uintImpl(bits div 2))
result = newTree(nnkBracketExpr, ident("UintImpl"), inner)
elif bits == 64:
result = ident("uint64")
elif bits == 32:
result = ident("uint32")
elif bits == 16:
result = ident("uint16")
elif bits == 8:
result = ident("uint8")
else:
error "Fatal: unreachable"
macro undistinct*(T: typedesc, recursive: static[bool] = false): untyped =
## reverses ``type T = distinct A``
let typeNode = getTypeImpl(T)
expectKind(typeNode, nnkBracketExpr)
if $typeNode[0] != "typeDesc":
error "expected typeDesc, got " & $typeNode[0]
var typeSym = typeNode[1]
if not recursive:
let impl = getTypeImpl(typeSym)
if $impl.typeKind != "ntyDistinct":
error "type is not distinct"
getTypeInst(impl[0])
else:
while true:
let impl = getTypeImpl(typeSym)
if $impl.typeKind != "ntyDistinct":
typeSym = impl
break
typeSym=getTypeInst(impl[0])
typeSym
type
# ### Private ### #
BaseUint* = UintImpl or SomeUnsignedInt
UintImpl*[Baseuint] = object
when system.cpuEndian == littleEndian:
lo*, hi*: BaseUint
else:
hi*, lo*: BaseUint
Uint[bits: static[int]] = distinct uintImpl(bits)
var a: Uint[128]
echo undistinct(type a)
# Error: cannot instantiate UintImpl
# got: <uint64>
# but expected: <Baseuint> This is another case of https://github.com/nim-lang/Nim/issues/7719 / nim-lang/RFCs#44
Instead making sure to generate fresh idents work. import macros, typetraits
macro uintImpl*(bits: static[int]): untyped =
# Release version, StUint[64] = uint64.
assert (bits and (bits-1)) == 0, $bits & " is not a power of 2"
assert bits >= 8, "The number of bits in a should be greater or equal to 8"
if bits >= 128:
let inner = getAST(uintImpl(bits div 2))
result = newTree(nnkBracketExpr, ident("UintImpl"), inner)
elif bits == 64:
result = ident("uint64")
elif bits == 32:
result = ident("uint32")
elif bits == 16:
result = ident("uint16")
elif bits == 8:
result = ident("uint8")
else:
error "Fatal: unreachable"
proc replaceNodes*(ast: NimNode): NimNode =
# Replace NimIdent and NimSym by a fresh ident node
proc inspect(node: NimNode): NimNode =
case node.kind:
of {nnkIdent, nnkSym}:
return ident($node)
of nnkEmpty:
return node
of nnkLiterals:
return node
else:
var rTree = node.kind.newTree()
for child in node:
rTree.add inspect(child)
return rTree
result = inspect(ast)
macro undistinct*(T: typedesc, recursive: static[bool] = false): untyped =
## reverses ``type T = distinct A``
runnableExamples:
import typetraits
type T = distinct int
doAssert undistinct(T) is int
doAssert: not compiles(undistinct(int))
type T2 = distinct T
doAssert undistinct(T2, recursive = false) is T
doAssert undistinct(int, recursive = true) is int
doAssert undistinct(T2, recursive = true) is int
let typeNode = getTypeImpl(T)
expectKind(typeNode, nnkBracketExpr)
if $typeNode[0] != "typeDesc":
error "expected typeDesc, got " & $typeNode[0]
var typeSym = typeNode[1]
if not recursive:
let impl = getTypeImpl(typeSym)
if $impl.typeKind != "ntyDistinct":
error "type is not distinct"
getTypeInst(impl[0]).replaceNodes # <---- fresh ident generator
else:
while true:
let impl = getTypeImpl(typeSym)
if $impl.typeKind != "ntyDistinct":
typeSym = impl
break
typeSym=getTypeInst(impl[0])
typeSym.replaceNodes # <---- fresh ident generator
type
# ### Private ### #
BaseUint* = UintImpl or SomeUnsignedInt
UintImpl*[Baseuint] = object
when system.cpuEndian == littleEndian:
lo*, hi*: BaseUint
else:
hi*, lo*: BaseUint
Uint[bits: static[int]] = distinct uintImpl(bits)
var a: Uint[128]
echo undistinct(type a) # UintImpl[system.uint64] |
|
61fe527
to
788f1f9
Compare
|
788f1f9
to
1663a21
Compare
@mratsim
I agree, let's leave it for another PR though: var x: distinct seq[seq[int]] # replace by a complex type
let y=x.undistinct
# would translate to:
let y=cast[x.type.undistinct](x)
# seems more efficient than `let y=(x.type.undistinct)(x) ?` |
@mratsim ok it was easy and useful enough so I added the type T = distinct int
var a: T = T(1)
let b = a.distinctBase NOTE: this PR reinforces my opinion that generics should only be used for IFTI (see https://github.com/nim-lang/Nim/issues/7517) |
17903bb
to
eb2e73a
Compare
@mratsim thanks a bunch for your stress test example and implementation fix! I've added your fix, cleaned it up a bit, and added a slightly simplified version of your stress test. PTAL |
Why would it be more efficient? Conversion from a distinct type shouldn't generate any code at all. |
EDIT |
lib/pure/sugar.nim
Outdated
proc inspect(node: NimNode): NimNode = | ||
case node.kind: | ||
of nnkIdent, nnkSym: | ||
return ident($node) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use result
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/pure/sugar.nim
Outdated
of nnkIdent, nnkSym: | ||
return ident($node) | ||
of nnkEmpty, nnkLiterals: | ||
return node |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use result
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/pure/sugar.nim
Outdated
var rTree = node.kind.newTree() | ||
for child in node: | ||
rTree.add inspect(child) | ||
return rTree |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use result
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/pure/sugar.nim
Outdated
return rTree | ||
result = inspect(ast) | ||
|
||
macro distinctBase*(T: typedesc, recursive: static[bool] = false): untyped = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should always be recursive. If I want to skip the 'distinct' indirections, I want to skip all of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should always be recursive. If I want to skip the 'distinct' indirections, I want to skip all of them.
same rationale as having both getTypeImpl
and getImpl
, plus other constructs where we want a direct vs a recursive behavior.
also, recursive = false is a saner default IMO:
distinctBase(T)
will give clean compile error in case T is not a distinct type (ie, catches bugs)
distinctBase(T, recursive = true)
will work (and recurse if applies), analog to the difference between "mkdir" and "mkdir -p"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well indeed, I don't see the point of "mkdir" vs "mkdir -p", just create the directory recursively and stop wasting my time. ;-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed it to be always recursive for the sake of moving on with this PR (but comments like "stop wasting my time" are not helpful as they don't address the points I mentioned: eg, some applications require the non-recursive getImpl
and cannot work with its recursive variant getTypeImpl
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sorry, but "stop wasting my time" referred to mkdir's design.
lib/pure/sugar.nim
Outdated
else: | ||
while true: | ||
let impl = getTypeImpl(typeSym) | ||
if $impl.typeKind != "ntyDistinct": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is if impl.typeKind != ntyDistinct
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/pure/sugar.nim
Outdated
var typeSym = typeNode[1] | ||
if not recursive: | ||
let impl = getTypeImpl(typeSym) | ||
if $impl.typeKind != "ntyDistinct": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is if impl.typeKind != ntyDistinct
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/pure/sugar.nim
Outdated
typeSym=getTypeInst(impl[0]) | ||
typeSym.replaceNodes | ||
|
||
proc distinctBase*[T](a: T, recursive: static[bool] = false): auto = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this proc? Please remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hint why is this suspicious: You cannot even write down its return type explicitly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To convert a variable without needing to do:
(type a.type.disctinctBase)(a)
, you would just use a.distinctBase
instead.
The signature can be changed to:
template distinctBase*[T](a: T, recursive: static[bool] = false): distinctBase(T, recursive) =
## converts a distinct variable to it's original type
runnableExamples:
type T = distinct int
var a: T = T(1)
let b = a.distinctBase
doAssert b is int
doAssert b == 1
distinctBase(T, recursive)(a)
Here I think func {.inline.} or template are better than simple proc, it's basically a no-op in the C backend.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Araq indeed, this feature was suggested by @mratsim and I agree with him that it's useful.
Changed to Here I think func {.inline.}
(not template, according to rule of preferring procs/funcs whenever possible)
basically a no-op in the C backend
I would hope with high enough optimization level the {.inline.}
would not make a difference though
You cannot even write down its return type explicitly.
that's because of #8545
so I kept auto
b226046
to
05e2900
Compare
PTAL, all comments addressed |
05e2900
to
24a4a9e
Compare
PTAL, all 2nd round comments addressed |
lib/pure/sugar.nim
Outdated
typeSym.replaceNodes | ||
|
||
# using `auto` return pending https://github.com/nim-lang/Nim/issues/8551 | ||
func distinctBase*[T](a: T): auto {.inline.} = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, why doesn't the macro version suffice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the discussion of that was here: #8531 (comment)
@mratsim had suggested it, and I added it because I agreed with him it made sense:
instead of (type(a).disctinctBase)(a)
, you would just use a.distinctBase
instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove it, all this bloat everywhere...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sigh... done.
24a4a9e
to
051c11f
Compare
PTAL |
lib/pure/sugar.nim
Outdated
of nnkEmpty, nnkLiterals: | ||
result = node | ||
else: | ||
var rTree = node.kind.newTree() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use result
here. That's why it exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done (thanks!)
lib/pure/sugar.nim
Outdated
@@ -198,3 +198,42 @@ macro dump*(x: typed): untyped = | |||
let r = quote do: | |||
debugEcho `s`, " = ", `x` | |||
return r | |||
|
|||
# TODO: consider exporting this in macros.nim | |||
proc replaceNodes(ast: NimNode): NimNode = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be called freshIdentNodes
or similar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
and fixed distinctBase with replaceNodes; simplified a bit implementation
051c11f
to
cf864d9
Compare
@Araq PTAL, tests completed and all comments addressed |
@mratsim since this is being revived here #13274 (comment) I was just testing and:
this seems to have disappeared bw 0.19.6 and 0.20.2, so that so the |
we should add a flag to make it non-recursive as option refs |
Assuming this is talking about |
/cc @andreaferretti @mratsim