Embed values for types with {.preservesEmbedded.}

This commit is contained in:
Emery Hemingway 2024-01-08 00:05:24 +02:00
parent 79ea25d1be
commit d2017228fb
2 changed files with 39 additions and 27 deletions

View File

@ -229,7 +229,7 @@ proc toDictionary*(pairs: openArray[(string, Value)]): Value =
for (key, val) in pairs: result[toSymbol(key)] = val for (key, val) in pairs: result[toSymbol(key)] = val
proc embed*(pr: sink Value): Value = proc embed*(pr: sink Value): Value =
## Create a Preserves value that embeds ``e``. ## Mark a Preserves value as embedded.
result = pr result = pr
result.embedded = true result.embedded = true
@ -328,7 +328,7 @@ template preservesLiteral*(value: typed) {.pragma.}
## See ``toPreserves``. ## See ``toPreserves``.
template preservesEmbedded*() {.pragma.} template preservesEmbedded*() {.pragma.}
## Pragma to mark a value as embedded by `toPreserves`. ## Pragma to mark a type or value as embedded.
template unpreservable*() {.pragma.} template unpreservable*() {.pragma.}
## Pragma to forbid a type from being converted by ``toPreserves``. ## Pragma to forbid a type from being converted by ``toPreserves``.
@ -375,11 +375,12 @@ proc toPreserves*[T](x: T): Value {.gcsafe.} =
elif T is Ordinal: elif T is Ordinal:
result = Value(kind: pkRegister, register: x.ord) result = Value(kind: pkRegister, register: x.ord)
assert result.register.T == x assert result.register.T == x
elif T is EmbeddedRef:
result = embed(x)
elif T is ptr | ref: elif T is ptr | ref:
if system.`==`(x, nil): result = initRecord("null") when T.hasCustomPragma(preservesEmbedded):
else: result = toPreserves(x[]) result = embed(x)
else:
if system.`==`(x, nil): result = initRecord("null")
else: result = toPreserves(x[])
elif T is string: elif T is string:
result = Value(kind: pkString, string: x) result = Value(kind: pkString, string: x)
elif T is SomeInteger: elif T is SomeInteger:
@ -390,15 +391,18 @@ proc toPreserves*[T](x: T): Value {.gcsafe.} =
elif T is distinct: elif T is distinct:
result = toPreserves(x.distinctBase) result = toPreserves(x.distinctBase)
elif T is object: elif T is object:
template applyEmbed(key: string; v: var Value) {.used.} = template fieldToPreserve[F](key: string; field: F): Value {.used.} =
when x.dot(key).hasCustomPragma(preservesEmbedded):
v.embedded = true
template fieldToPreserve(key: string; val: typed): Value {.used.} =
when x.dot(key).hasCustomPragma(preservesLiteral): when x.dot(key).hasCustomPragma(preservesLiteral):
const atom = x.dot(key).getCustomPragmaVal(preservesLiteral).parsePreservesAtom const atom = x.dot(key).getCustomPragmaVal(preservesLiteral).parsePreservesAtom
atom.toPreservesHook() when x.dot(key).hasCustomPragma(preservesEmbedded):
atom.toPreservesHook().embed
else:
atom.toPreservesHook()
elif x.dot(key).hasCustomPragma(preservesEmbedded) and F is EmbeddedRef:
embed(field)
else: else:
toPreserves(val) field.toPreserves
# checking for the embedded pragma here yields false positives
when T.hasCustomPragma(unpreservable): when T.hasCustomPragma(unpreservable):
raiseAssert($T & " is unpreservable") raiseAssert($T & " is unpreservable")
elif T.hasCustomPragma(preservesOr): elif T.hasCustomPragma(preservesOr):
@ -410,13 +414,11 @@ proc toPreserves*[T](x: T): Value {.gcsafe.} =
else: else:
assert(hasKind and not hasVariant) assert(hasKind and not hasVariant)
result = fieldToPreserve(k, v) result = fieldToPreserve(k, v)
applyEmbed(k, result)
hasVariant = true hasVariant = true
elif T.hasCustomPragma(preservesRecord): elif T.hasCustomPragma(preservesRecord):
result = Value(kind: pkRecord) result = Value(kind: pkRecord)
for k, v in x.fieldPairs: for k, v in x.fieldPairs:
var pr = fieldToPreserve(k, v) var pr = fieldToPreserve(k, v)
applyEmbed(k, pr)
result.record.add(pr) result.record.add(pr)
result.record.add(toSymbol(T.getCustomPragmaVal(preservesRecord))) result.record.add(toSymbol(T.getCustomPragmaVal(preservesRecord)))
elif T.hasCustomPragma(preservesTuple): elif T.hasCustomPragma(preservesTuple):
@ -428,7 +430,6 @@ proc toPreserves*[T](x: T): Value {.gcsafe.} =
# TODO: what if there are fields after the tail? # TODO: what if there are fields after the tail?
else: else:
var pr = fieldToPreserve(label, field) var pr = fieldToPreserve(label, field)
applyEmbed(label, pr)
result.sequence.add(pr) result.sequence.add(pr)
elif T.hasCustomPragma(preservesDictionary): elif T.hasCustomPragma(preservesDictionary):
result = initDictionary() result = initDictionary()
@ -436,11 +437,13 @@ proc toPreserves*[T](x: T): Value {.gcsafe.} =
when val is Option: when val is Option:
if val.isSome: if val.isSome:
var pr = fieldToPreserve(key, val.get) var pr = fieldToPreserve(key, val.get)
applyEmbed(key, pr) if x.dot(key).hasCustomPragma(preservesEmbedded):
pr.embedded = true
result[key.toSymbol] = pr result[key.toSymbol] = pr
else: else:
var pr = fieldToPreserve(key, val) var pr = fieldToPreserve(key, val)
applyEmbed(key, pr) if x.dot(key).hasCustomPragma(preservesEmbedded):
pr.embedded = true
result[key.toSymbol] = pr result[key.toSymbol] = pr
sortDict(result) sortDict(result)
else: else:
@ -661,19 +664,27 @@ proc fromPreserves*[T](v: var T; pr: Value): bool {.gcsafe.} =
else: false else: false
if not result: break if not result: break
else: discard else: discard
elif T is EmbeddedRef:
v = T(pr.embeddedRef)
result = true
elif T is ref: elif T is ref:
if isNil(v): new(v) when T.hasCustomPragma(preservesEmbedded):
result = fromPreserves(v[], pr) if (pr.kind == pkEmbedded) and (pr.embeddedRef of T):
v = T(pr.embeddedRef)
result = true
else:
if isNil(v): new(v)
result = fromPreserves(v[], pr)
elif T is object: elif T is object:
template fieldFromPreserve(key: string; val: typed; pr: Value): bool {.used.} = template fieldFromPreserve[T](key: string; val: T; pr: Value): bool {.used.} =
when v.dot(key).hasCustomPragma(preservesLiteral): when v.dot(key).hasCustomPragma(preservesLiteral):
const atom = v.dot(key).getCustomPragmaVal(preservesLiteral).parsePreservesAtom const atom = v.dot(key).getCustomPragmaVal(preservesLiteral).parsePreservesAtom
pr == atom.toPreservesHook() pr == atom.toPreservesHook()
else: elif v.dot(key).hasCustomPragma(preservesEmbedded):
fromPreserves(val, pr) when T is EmbeddedRef:
if pr.kind == pkEmbedded and pr.embeddedRef of T:
val = T(pr.embeddedRef)
true
else: false
else: fromPreserves(val, pr)
else: fromPreserves(val, pr)
when T.hasCustomPragma(unpreservable): when T.hasCustomPragma(unpreservable):
raiseAssert($T & " is unpreservable") raiseAssert($T & " is unpreservable")
elif T.hasCustomPragma(preservesRecord): elif T.hasCustomPragma(preservesRecord):
@ -726,7 +737,8 @@ proc fromPreserves*[T](v: var T; pr: Value): bool {.gcsafe.} =
inc i inc i
result = result and val.isSome result = result and val.isSome
if result: if result:
result = result and fieldFromPreserve(key, v.dot(key), val.get) var pr = val.get
result = result and fieldFromPreserve(key, v.dot(key), pr)
result = result and (i <= pr.len) result = result and (i <= pr.len)
elif T.hasCustomPragma(preservesOr): elif T.hasCustomPragma(preservesOr):
for kind in typeof(T.orKind): for kind in typeof(T.orKind):

View File

@ -79,7 +79,7 @@ suite "conversions":
type type
Foo {.preservesRecord: "foo".} = object Foo {.preservesRecord: "foo".} = object
n: int n: int
bar: Bar bar {.preservesEmbedded.}: Bar
Bar = ref object of RootObj Bar = ref object of RootObj
x: int x: int
Baz = ref object of RootObj Baz = ref object of RootObj