Unverified Commit 32d5243a authored by Eric Myhre's avatar Eric Myhre Committed by GitHub

Merge pull request #60 from ipld/more-codegen

Codegen of unions, and their keyed representations
parents 547281ab ce2ef766
......@@ -84,7 +84,9 @@ type MapAssembler interface {
// For plain maps (that is, not structs or unions masquerading as maps),
// the empty string can be used as a parameter, and the returned NodePrototype
// can be assumed applicable for all values.
// Using an empty string for a struct or union will return a nil NodePrototype.
// Using an empty string for a struct or union will return nil,
// as will using any string which isn't a field or member of those types.
//
// (Design note: a string is sufficient for the parameter here rather than
// a full Node, because the only cases where the value types vary are also
// cases where the keys may not be complex.)
......
......@@ -4,6 +4,21 @@ import (
"fmt"
)
// TODO: errors in this package remain somewhat slapdash.
//
// - ipld.ErrUnmatchable is used as a catch-all in some places, and contains who-knows-what values wrapped in the Reason field.
// - sometimes this wraps things like strconv errors... and on the one hand, i'm kinda okay with that; on the other, maybe saying a bit more with types before getting to that kind of shrug would be nice.
// - we probably want to use `Type` values, right?
// - or do we: because then we probably need a `Repr bool` next to it, or lots of messages would be nonsensical.
// - this is *currently* problematic because we don't actually generate type info consts yet. Hopefully soon; but the pain, meanwhile, is... substantial.
// - "substantial" is an understatement. it makes incremental development almost impossible because stringifying error reports turn into nil pointer crashes!
// - other ipld-wide errors like `ipld.ErrWrongKind` *sometimes* refer to a TypeName... but don't *have* to, because they also arise at the merely-datamodel level; what would we do with these?
// - it's undesirable (not to mention intensely forbidden for import cycle reasons) for those error types to refer to schema.Type.
// - if we must have TypeName treated stringily in some cases, is it really useful to use full type info in other cases -- inconsistently?
// - regardless of where we end up with this, some sort of an embed for helping deal with munging and printing this would probably be wise.
// - generally, whether you should expect an "ipld.Err*" or a "schema.Err*" from various methods is quite unclear.
// - it's possible that we should wrap *all* schema-level errors in a single "ipld.ErrSchemaNoMatch" error of some kind, to fix the above. as yet undecided.
// ErrNoSuchField may be returned from lookup functions on the Node
// interface when a field is requested which doesn't exist, or from Insert
// on a MapBuilder when a key doesn't match a field name in the structure.
......@@ -19,3 +34,24 @@ func (e ErrNoSuchField) Error() string {
}
return fmt.Sprintf("no such field: %s.%s", e.Type.Name(), e.FieldName)
}
// ErrNotUnionStructure means data was fed into a union assembler that can't match the union.
//
// This could have one of several reasons, which are explained in the detail text:
//
// - there are too many entries in the map;
// - the keys of critical entries aren't found;
// - keys are found that aren't any of the expected critical keys;
// - etc.
//
// TypeName is currently a string... see comments at the top of this file for
// remarks on the issues we need to address about these identifiers in errors in general.
type ErrNotUnionStructure struct {
TypeName string
Detail string
}
func (e ErrNotUnionStructure) Error() string {
return fmt.Sprintf("cannot match schema: union structure constraints for %s caused rejection: %s", e.TypeName, e.Detail)
}
......@@ -31,3 +31,121 @@ the existing allowNull), and another for both (for "nullable optional" fields).
Every NodeAssembler would then have to support that, just as they each support allowNull now.
I think the above design is valid, but it's not implemented nor tested yet.
### AssignNode optimality
The AssignNode methods we generate currently do pretty blithe things with large structures:
they iterate over the given node, and hurl entries into the assembler's AssignKey and AssignValue methods.
This isn't always optimal.
For any structure that is more efficient when fed info in an ideal order, we might want to take account of that.
For example, unions with representation mode "inline" are a stellar example of this:
if the discriminant key comes first, they can work *much, much* more efficiently.
By contrast, if the discriminant key shows up late in the object, it is necessary to
have buffered *all the other* data, then backtrack to handle it once the discriminant is found and parsed.
At best, this probably means iterating once, plucking out the discriminant entry,
and then *getting a new iterator* that starts from the beginning (which shifts
the buffer problem to the Node we're consuming data from).
Even more irritatingly: since NodeAssembler has to accept entries in any order
if it is to accept information streamingly from codecs, the NodeAssembler
*also* has to be ready to do the buffering work...
TODO ouch what are the ValueAssembler going to yield for dealing with children?
TODO we have to hand out dummy ValueAssembler types that buffer... a crazy amount of stuff. (Reinvent refmt.Tok?? argh.) cannot avoid???
TODO this means where errors arise from will be nuts: you cant say if anything is wrong until you figure out the discriminant. then we replay everything? your errors for deeper stuff will appear... uh... midway, from a random AssembleValue finishing that happens to be for the discriminant. that is not pleasant.
... let's leave that thought aside: suffice to say, some assemblers are *really*
not happy or performant if they have to accept things in unpleasant orderings.
So.
We should flip all this on its head. The AssignNode methods should lean in
on the knowledge they have about the structure they're building, and assume
that the Node we're copying content from supports random access:
pluck the fields that we care most about out first with direct lookups,
and only use iteration to cover the remaining data that the new structure
doesn't care about the ordering of.
Perhaps this only matters for certain styles of unions.
### sidenote about codec interfaces
Perhaps we should get used to the idea of codec packages offering two styles of methods:
- `UnmarshalIntoAssembler(io.Reader, ipld.NodeAssembler) error`
- this is for when you have opinions about what kind of in-memory format should be used
- `Unmarshal(io.Reader) (ipld.Node, error)`
- this is for when you want to let the codec pick.
We might actually end up preferring the latter in a fair number of cases.
Looking at this inline union ordering situation described above:
the best path through that (other than saying "don't fking use inline unions,
and if you do, put the discriminant in the first fking entry or gtfo") would probably be
to do a cbor (or whatever) unmarshal that produces the half-deserialized skip-list nodes
(which are specialized to the cbor format rather than general purpose, but we want that in this story)...
and those can then claim to do random access, thereby letting them take on the "buffering".
This approach would let the serialization-specialized nodes take on the work,
rather than forcing the union's NodeAssembler to do buffer at a higher level...
which is good because doing that buffering in a structured way at a higher level
is actually more work and causes more memory fragmentation and allocations.
Whew.
I have not worked out what this implies for multicodecs or other muxes that do compositions of codecs.
### enums of union keys
It's extremely common to have an enum that is the discrimant values of a union.
We should make a schema syntax for that.
We tend to generate such an enum in codegen anyway, for various purposes.
Might as well let people name it outright too, if they have the slightest desire to do so.
(Doesn't apply to kinded unions.)
### can reset methods be replaced with duff's device?
Yes. Well, sort of. Okay, no.
It's close! Assemblers were all written such that their zero values are ready to go.
However, there's a couple of situations where you *wouldn't* want to blithely zero everything:
for example, if an assembler has to do some allocations, but they're reusable,
you wouldn't want to turn those other objects into garbage by zeroing the pointer to them.
See the following section about new-alloc child assemblers for an example of this.
### what's up with new-alloc child assemblers?
Mostly, child assemblers are embedded in the assembler for the type that contains them;
this is part of our allocation amortization strategy and important to performance.
However, it doesn't always apply:
Sometimes we *need* independently allocated assemblers, even when they're child assemblers:
recursive structures need this (otherwise, how big would the slab be? infinite? no; halt).
Sometimes we also just *want* them, somewhat more mildly: if a union is one of several things,
and some of them are uncommonly used but huuuuge, then maybe we'd rather allocate the child assemblers
individually on demand rather than pay a large resident memory cost to embed all the possibilities.
There's a couple things to think about with these:
- resetting assemblers with a duff's device strategy wouldn't recursively reset these;
it would just orphan them. While possibly leaving them pointed into some parts of memory in the parent slab ('cm' in particular comes to mind).
This could be a significant correctness issue.
- But who's responsibility is it to "safe" this? Nilling 'w' proactively should also make this pretty innocuous, as one option (but we don't currently do this).
- if the parent assembler is being used in some highly reusable situation (e.g. it's a list value or map value),
is the parent able to hold onto and re-use the child assembler? We probably usually still want to do this, even if it's in a separate piece of heap.
- For unions, there's a question of if we should hold onto each child assembler, or just the most recent; that's a choice we could make and tune.
If the answer is "most recent only", we could even crank down the resident size by use of more interfaces instead of concrete types (at the cost of some other runtime performance debufs, most likely).
We've chosen to discard the possibility of duff's device as an assembler resetting implementation.
As a result, we don't have to do proactive 'w'-nil'ing in places we might otherwise have to.
And union assemblers hold on to all child assembler types they've ever needed.
......@@ -92,8 +92,8 @@ Legend:
| feature | accessors | builders |
|:-------------------------------|:---------:|:--------:|
| unions | ... | ... |
| ... type level | | |
| ... keyed representation | | |
| ... type level | | |
| ... keyed representation | | |
| ... envelope representation | ✘ | ✘ |
| ... kinded representation | ✘ | ✘ |
| ... inline representation | ✘ | ✘ |
......
package gengo
import (
"fmt"
"strings"
"github.com/ipld/go-ipld-prime/schema"
)
// This entire file is placeholder-quality implementations.
//
// The AdjunctCfg struct should be replaced with an IPLD Schema-specified thing!
// The values in the unionMemlayout field should be an enum;
// etcetera!
type FieldTuple struct {
TypeName schema.TypeName
FieldName string
......@@ -15,10 +22,13 @@ type AdjunctCfg struct {
typeSymbolOverrides map[schema.TypeName]string
fieldSymbolLowerOverrides map[FieldTuple]string
fieldSymbolUpperOverrides map[FieldTuple]string
maybeUsesPtr map[schema.TypeName]bool // treat absent as true
maybeUsesPtr map[schema.TypeName]bool // treat absent as true
unionMemlayout map[schema.TypeName]string // "embedAll"|"interface"; maybe more options later, unclear for now.
// note: PkgName doesn't appear in here, because it's...
// not adjunct data. it's a generation invocation parameter.
// ... this might not hold up in the future though.
// There are unanswered questions about how (also, tbf, *if*) we'll handle generation of multiple packages which use each other's types.
}
// TypeSymbol returns the symbol for a type;
......@@ -64,3 +74,24 @@ func (cfg *AdjunctCfg) MaybeUsesPtr(t schema.Type) bool {
// Perhaps structs and unions are the only things likely to benefit from pointers.
return true
}
// UnionMemlayout returns a plain string at present;
// there's a case-switch in the templates that processes it.
// We validate that it's a known string when this method is called.
// This should probably be improved in type-safety,
// and validated more aggressively up front when adjcfg is loaded.
func (cfg *AdjunctCfg) UnionMemlayout(t schema.Type) string {
if t.Kind() != schema.Kind_Union {
panic(fmt.Errorf("%s is not a union!", t.Name()))
}
v, ok := cfg.unionMemlayout[t.Name()]
if !ok {
return "embedAll"
}
switch v {
case "embedAll", "interface":
return v
default:
panic(fmt.Errorf("invalid config: unionMemlayout values must be either \"embedAll\" or \"interface\", not %q", v))
}
}
......@@ -272,28 +272,7 @@ func (g structBuilderGenerator) EmitNodeAssemblerType(w io.Writer) {
`, w, g.AdjCfg, g)
}
func (g structBuilderGenerator) EmitNodeAssemblerMethodBeginMap(w io.Writer) {
// We currently disregard sizeHint. It's not relevant to us.
// We could check it strictly and emit errors; presently, we don't.
// This method contains a branch to support MaybeUsesPtr because new memory may need to be allocated.
// This allocation only happens if the 'w' ptr is nil, which means we're being used on a Maybe;
// otherwise, the 'w' ptr should already be set, and we fill that memory location without allocating, as usual.
doTemplate(`
func (na *_{{ .Type | TypeSymbol }}__Assembler) BeginMap(int) (ipld.MapAssembler, error) {
switch *na.m {
case schema.Maybe_Value, schema.Maybe_Null:
panic("invalid state: cannot assign into assembler that's already finished")
case midvalue:
panic("invalid state: it makes no sense to 'begin' twice on the same assembler!")
}
*na.m = midvalue
{{- if .Type | MaybeUsesPtr }}
if na.w == nil {
na.w = &_{{ .Type | TypeSymbol }}{}
}
{{- end}}
return na, nil
}
`, w, g.AdjCfg, g)
emitNodeAssemblerMethodBeginMap_strictoid(w, g.AdjCfg, g)
}
func (g structBuilderGenerator) EmitNodeAssemblerMethodAssignNull(w io.Writer) {
emitNodeAssemblerMethodAssignNull_recursive(w, g.AdjCfg, g)
......
......@@ -324,28 +324,7 @@ func (g structReprMapReprBuilderGenerator) EmitNodeAssemblerType(w io.Writer) {
`, w, g.AdjCfg, g)
}
func (g structReprMapReprBuilderGenerator) EmitNodeAssemblerMethodBeginMap(w io.Writer) {
// We currently disregard sizeHint. It's not relevant to us.
// We could check it strictly and emit errors; presently, we don't.
// This method contains a branch to support MaybeUsesPtr because new memory may need to be allocated.
// This allocation only happens if the 'w' ptr is nil, which means we're being used on a Maybe;
// otherwise, the 'w' ptr should already be set, and we fill that memory location without allocating, as usual.
doTemplate(`
func (na *_{{ .Type | TypeSymbol }}__ReprAssembler) BeginMap(int) (ipld.MapAssembler, error) {
switch *na.m {
case schema.Maybe_Value, schema.Maybe_Null:
panic("invalid state: cannot assign into assembler that's already finished")
case midvalue:
panic("invalid state: it makes no sense to 'begin' twice on the same assembler!")
}
*na.m = midvalue
{{- if .Type | MaybeUsesPtr }}
if na.w == nil {
na.w = &_{{ .Type | TypeSymbol }}{}
}
{{- end}}
return na, nil
}
`, w, g.AdjCfg, g)
emitNodeAssemblerMethodBeginMap_strictoid(w, g.AdjCfg, g)
}
func (g structReprMapReprBuilderGenerator) EmitNodeAssemblerMethodAssignNull(w io.Writer) {
emitNodeAssemblerMethodAssignNull_recursive(w, g.AdjCfg, g)
......
This diff is collapsed.
package gengo
import (
"io"
"github.com/ipld/go-ipld-prime/schema"
"github.com/ipld/go-ipld-prime/schema/gen/go/mixins"
)
var _ TypeGenerator = &unionReprKeyedGenerator{}
// General observation: many things about the keyed representation of unions is *very* similar to the type-level code,
// because the type level code effective does espouse keyed-style behavior (just with type names as the keys).
// Be advised that this similarity does not hold at *all* true of any of the other representation modes of unions!
func NewUnionReprKeyedGenerator(pkgName string, typ schema.TypeUnion, adjCfg *AdjunctCfg) TypeGenerator {
return unionReprKeyedGenerator{
unionGenerator{
adjCfg,
mixins.MapTraits{
pkgName,
string(typ.Name()),
adjCfg.TypeSymbol(typ),
},
pkgName,
typ,
},
}
}
type unionReprKeyedGenerator struct {
unionGenerator
}
func (g unionReprKeyedGenerator) GetRepresentationNodeGen() NodeGenerator {
return unionReprKeyedReprGenerator{
g.AdjCfg,
mixins.MapTraits{
g.PkgName,
string(g.Type.Name()) + ".Repr",
"_" + g.AdjCfg.TypeSymbol(g.Type) + "__Repr",
},
g.PkgName,
g.Type,
}
}
type unionReprKeyedReprGenerator struct {
AdjCfg *AdjunctCfg
mixins.MapTraits
PkgName string
Type schema.TypeUnion
}
func (unionReprKeyedReprGenerator) IsRepr() bool { return true } // hint used in some generalized templates.
func (g unionReprKeyedReprGenerator) EmitNodeType(w io.Writer) {
// The type is structurally the same, but will have a different set of methods.
doTemplate(`
type _{{ .Type | TypeSymbol }}__Repr _{{ .Type | TypeSymbol }}
`, w, g.AdjCfg, g)
// We do also want some constants for our discriminant values;
// they'll make iterators able to work faster.
doTemplate(`
var (
{{- range $member := .Type.Members }}
memberName__{{ dot.Type | TypeSymbol }}_{{ $member.Name }}_serial = _String{"{{ $member | dot.Type.RepresentationStrategy.GetDiscriminant }}"}
{{- end }}
)
`, w, g.AdjCfg, g)
}
func (g unionReprKeyedReprGenerator) EmitNodeTypeAssertions(w io.Writer) {
doTemplate(`
var _ ipld.Node = &_{{ .Type | TypeSymbol }}__Repr{}
`, w, g.AdjCfg, g)
}
func (g unionReprKeyedReprGenerator) EmitNodeMethodLookupByString(w io.Writer) {
// Similar to the type-level method, except uses discriminant values as keys instead of the member type names.
doTemplate(`
func (n *_{{ .Type | TypeSymbol }}__Repr) LookupByString(key string) (ipld.Node, error) {
switch key {
{{- range $i, $member := .Type.Members }}
case "{{ $member | dot.Type.RepresentationStrategy.GetDiscriminant }}":
{{- if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "embedAll") }}
if n.tag != {{ add $i 1 }} {
return nil, ipld.ErrNotExists{ipld.PathSegmentOfString(key)}
}
return &n.x{{ add $i 1 }}, nil
{{- else if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "interface") }}
if n2, ok := n.x.({{ $member | TypeSymbol }}); ok {
return n2, nil
} else {
return nil, ipld.ErrNotExists{ipld.PathSegmentOfString(key)}
}
{{- end}}
{{- end}}
default:
return nil, schema.ErrNoSuchField{Type: nil /*TODO*/, FieldName: key}
}
}
`, w, g.AdjCfg, g)
}
func (g unionReprKeyedReprGenerator) EmitNodeMethodLookupByNode(w io.Writer) {
doTemplate(`
func (n *_{{ .Type | TypeSymbol }}__Repr) LookupByNode(key ipld.Node) (ipld.Node, error) {
ks, err := key.AsString()
if err != nil {
return nil, err
}
return n.LookupByString(ks)
}
`, w, g.AdjCfg, g)
}
func (g unionReprKeyedReprGenerator) EmitNodeMethodMapIterator(w io.Writer) {
// Similar to the type-level method, except yields discriminant values as keys instead of the member type names.
doTemplate(`
func (n *_{{ .Type | TypeSymbol }}__Repr) MapIterator() ipld.MapIterator {
return &_{{ .Type | TypeSymbol }}__ReprMapItr{n, false}
}
type _{{ .Type | TypeSymbol }}__ReprMapItr struct {
n *_{{ .Type | TypeSymbol }}__Repr
done bool
}
func (itr *_{{ .Type | TypeSymbol }}__ReprMapItr) Next() (k ipld.Node, v ipld.Node, _ error) {
if itr.done {
return nil, nil, ipld.ErrIteratorOverread{}
}
{{- if (eq (.AdjCfg.UnionMemlayout .Type) "embedAll") }}
switch itr.n.tag {
{{- range $i, $member := .Type.Members }}
case {{ add $i 1 }}:
return &memberName__{{ dot.Type | TypeSymbol }}_{{ $member.Name }}_serial, itr.n.x{{ add $i 1 }}.Representation(), nil
{{- end}}
{{- else if (eq (.AdjCfg.UnionMemlayout .Type) "interface") }}
switch n2 := itr.n.x.(type) {
{{- range $member := .Type.Members }}
case {{ $member | TypeSymbol }}:
return &memberName__{{ dot.Type | TypeSymbol }}_{{ $member.Name }}_serial, n2.Representation(), nil
{{- end}}
{{- end}}
default:
panic("unreachable")
}
itr.done = true
return
}
func (itr *_{{ .Type | TypeSymbol }}__ReprMapItr) Done() bool {
return itr.done
}
`, w, g.AdjCfg, g)
}
func (g unionReprKeyedReprGenerator) EmitNodeMethodLength(w io.Writer) {
doTemplate(`
func (_{{ .Type | TypeSymbol }}__Repr) Length() int {
return 1
}
`, w, g.AdjCfg, g)
}
func (g unionReprKeyedReprGenerator) EmitNodeMethodPrototype(w io.Writer) {
emitNodeMethodPrototype_typical(w, g.AdjCfg, g)
}
func (g unionReprKeyedReprGenerator) EmitNodePrototypeType(w io.Writer) {
emitNodePrototypeType_typical(w, g.AdjCfg, g)
}
// --- NodeBuilder and NodeAssembler --->
func (g unionReprKeyedReprGenerator) GetNodeBuilderGenerator() NodeBuilderGenerator {
return unionReprKeyedReprBuilderGenerator{
g.AdjCfg,
mixins.MapAssemblerTraits{
g.PkgName,
g.TypeName,
"_" + g.AdjCfg.TypeSymbol(g.Type) + "__Repr",
},
g.PkgName,
g.Type,
}
}
type unionReprKeyedReprBuilderGenerator struct {
AdjCfg *AdjunctCfg
mixins.MapAssemblerTraits
PkgName string
Type schema.TypeUnion
}
func (unionReprKeyedReprBuilderGenerator) IsRepr() bool { return true } // hint used in some generalized templates.
func (g unionReprKeyedReprBuilderGenerator) EmitNodeBuilderType(w io.Writer) {
emitEmitNodeBuilderType_typical(w, g.AdjCfg, g)
}
func (g unionReprKeyedReprBuilderGenerator) EmitNodeBuilderMethods(w io.Writer) {
emitNodeBuilderMethods_typical(w, g.AdjCfg, g)
}
func (g unionReprKeyedReprBuilderGenerator) EmitNodeAssemblerType(w io.Writer) {
// Nearly identical to the type-level system, except it embeds the Repr variant of child assemblers
// (which is a very minor difference textually, but means this structure can end up with a pretty wildly different resident memory size than the type-level one).
doTemplate(`
type _{{ .Type | TypeSymbol }}__ReprAssembler struct {
w *_{{ .Type | TypeSymbol }}
m *schema.Maybe
state maState
cm schema.Maybe
{{- range $i, $member := .Type.Members }}
ca{{ add $i 1 }} {{ if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "interface") }}*{{end}}_{{ $member | TypeSymbol }}__ReprAssembler
{{end -}}
ca uint
}
`, w, g.AdjCfg, g)
// Reset methods: also nearly identical to the type-level ones.
doTemplate(`
func (na *_{{ .Type | TypeSymbol }}__ReprAssembler) reset() {
na.state = maState_initial
switch na.ca {
case 0:
return
{{- range $i, $member := .Type.Members }}
case {{ add $i 1 }}:
na.ca{{ add $i 1 }}.reset()
{{end -}}
default:
panic("unreachable")
}
na.cm = schema.Maybe_Absent
}
`, w, g.AdjCfg, g)
}
func (g unionReprKeyedReprBuilderGenerator) EmitNodeAssemblerMethodBeginMap(w io.Writer) {
emitNodeAssemblerMethodBeginMap_strictoid(w, g.AdjCfg, g)
}
func (g unionReprKeyedReprBuilderGenerator) EmitNodeAssemblerMethodAssignNull(w io.Writer) {
// It might sound a bit odd to call a union "recursive", since it's so very trivially so (no fan-out),
// but it's functionally accurate: the generated method should include a branch for the 'midvalue' state.
emitNodeAssemblerMethodAssignNull_recursive(w, g.AdjCfg, g)
}
func (g unionReprKeyedReprBuilderGenerator) EmitNodeAssemblerMethodAssignNode(w io.Writer) {
// DRY: this is once again not-coincidentally very nearly equal to the type-level method. Would be good to dedup them... after we do the get-to-the-point-in-phase-3 improvement.
doTemplate(`
func (na *_{{ .Type | TypeSymbol }}__ReprAssembler) AssignNode(v ipld.Node) error {
if v.IsNull() {
return na.AssignNull()
}
if v2, ok := v.(*_{{ .Type | TypeSymbol }}); ok {
switch *na.m {
case schema.Maybe_Value, schema.Maybe_Null:
panic("invalid state: cannot assign into assembler that's already finished")
case midvalue:
panic("invalid state: cannot assign null into an assembler that's already begun working on recursive structures!")
}
{{- if .Type | MaybeUsesPtr }}
if na.w == nil {
na.w = v2
*na.m = schema.Maybe_Value
return nil
}
{{- end}}
*na.w = *v2
*na.m = schema.Maybe_Value
return nil
}
if v.ReprKind() != ipld.ReprKind_Map {
return ipld.ErrWrongKind{TypeName: "{{ .PkgName }}.{{ .Type.Name }}.Repr", MethodName: "AssignNode", AppropriateKind: ipld.ReprKindSet_JustMap, ActualKind: v.ReprKind()}
}
itr := v.MapIterator()
for !itr.Done() {
k, v, err := itr.Next()
if err != nil {
return err
}
if err := na.AssembleKey().AssignNode(k); err != nil {
return err
}
if err := na.AssembleValue().AssignNode(v); err != nil {
return err
}
}
return na.Finish()
}
`, w, g.AdjCfg, g)
}
func (g unionReprKeyedReprBuilderGenerator) EmitNodeAssemblerOtherBits(w io.Writer) {
g.emitMapAssemblerChildTidyHelper(w)
g.emitMapAssemblerMethods(w)
g.emitKeyAssembler(w)
}
func (g unionReprKeyedReprBuilderGenerator) emitMapAssemblerChildTidyHelper(w io.Writer) {
// Nearly identical to the type-level equivalent.
doTemplate(`
func (ma *_{{ .Type | TypeSymbol }}__ReprAssembler) valueFinishTidy() bool {
switch ma.cm {
case schema.Maybe_Value:
{{- /* nothing to do for memlayout=embedAll; the tag is already set and memory already in place. */ -}}
{{- /* nothing to do for memlayout=interface either; same story, the values are already in place. */ -}}
ma.state = maState_initial
return true
default:
return false
}
}
`, w, g.AdjCfg, g)
}
func (g unionReprKeyedReprBuilderGenerator) emitMapAssemblerMethods(w io.Writer) {
// All of these: shamelessly similar to the type-level equivalent, modulo a few appearances of "Repr".
// Alright, and also the "discriminant values as keys instead of the member type names" thing.
// DRY: the number of times these `ma.state` switches are appearing is truly intense! This is starting to look like one of them most important things to shrink the GSLOC/ASM size of!
doTemplate(`
func (ma *_{{ .Type | TypeSymbol }}__ReprAssembler) AssembleEntry(k string) (ipld.NodeAssembler, error) {
switch ma.state {
case maState_initial:
// carry on
case maState_midKey:
panic("invalid state: AssembleEntry cannot be called when in the middle of assembling another key")
case maState_expectValue:
panic("invalid state: AssembleEntry cannot be called when expecting start of value assembly")
case maState_midValue:
if !ma.valueFinishTidy() {
panic("invalid state: AssembleEntry cannot be called when in the middle of assembling a value")
} // if tidy success: carry on for the moment, but we'll still be erroring shortly.
case maState_finished:
panic("invalid state: AssembleEntry cannot be called on an assembler that's already finished")
}
if ma.ca != 0 {
return nil, schema.ErrNotUnionStructure{TypeName:"{{ .PkgName }}.{{ .Type.Name }}.Repr", Detail: "cannot add another entry -- a union can only contain one thing!"}
}
switch k {
{{- range $i, $member := .Type.Members }}
case "{{ $member | dot.Type.RepresentationStrategy.GetDiscriminant }}":
ma.state = maState_midValue
ma.ca = {{ add $i 1 }}
{{- if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "embedAll") }}
ma.w.tag = {{ add $i 1 }}
ma.ca{{ add $i 1 }}.w = &ma.w.x{{ add $i 1 }}
ma.ca{{ add $i 1 }}.m = &ma.cm
return &ma.ca{{ add $i 1 }}, nil
{{- else if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "interface") }}
x := &_{{ $member | TypeSymbol }}{}
ma.w.x = x
if ma.ca{{ add $i 1 }} == nil {
ma.ca{{ add $i 1 }} = &_{{ $member | TypeSymbol }}__Assembler{}
}
ma.ca{{ add $i 1 }}.w = x
ma.ca{{ add $i 1 }}.m = &ma.cm
return ma.ca{{ add $i 1 }}, nil
{{- end}}
{{- end}}
default:
return nil, ipld.ErrInvalidKey{TypeName:"{{ .PkgName }}.{{ .Type.Name }}.Repr", Key:&_String{k}}
}
}
`, w, g.AdjCfg, g)
doTemplate(`
func (ma *_{{ .Type | TypeSymbol }}__ReprAssembler) AssembleKey() ipld.NodeAssembler {
switch ma.state {
case maState_initial:
// carry on
case maState_midKey:
panic("invalid state: AssembleKey cannot be called when in the middle of assembling another key")
case maState_expectValue:
panic("invalid state: AssembleKey cannot be called when expecting start of value assembly")
case maState_midValue:
if !ma.valueFinishTidy() {
panic("invalid state: AssembleKey cannot be called when in the middle of assembling a value")
} // if tidy success: carry on for the moment, but we'll still be erroring shortly... or rather, the keyassembler will be.
case maState_finished:
panic("invalid state: AssembleKey cannot be called on an assembler that's already finished")
}
ma.state = maState_midKey
return (*_{{ .Type | TypeSymbol }}__ReprKeyAssembler)(ma)
}
`, w, g.AdjCfg, g)
doTemplate(`
func (ma *_{{ .Type | TypeSymbol }}__ReprAssembler) AssembleValue() ipld.NodeAssembler {
switch ma.state {
case maState_initial:
panic("invalid state: AssembleValue cannot be called when no key is primed")
case maState_midKey:
panic("invalid state: AssembleValue cannot be called when in the middle of assembling a key")
case maState_expectValue:
// carry on
case maState_midValue:
panic("invalid state: AssembleValue cannot be called when in the middle of assembling another value")
case maState_finished:
panic("invalid state: AssembleValue cannot be called on an assembler that's already finished")
}
ma.state = maState_midValue
switch ma.ca {
{{- range $i, $member := .Type.Members }}
case {{ $i }}:
{{- if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "embedAll") }}
ma.ca{{ add $i 1 }}.w = &ma.w.x{{ add $i 1 }}
ma.ca{{ add $i 1 }}.m = &ma.cm
return &ma.ca{{ add $i 1 }}
{{- else if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "interface") }}
x := &_{{ $member | TypeSymbol }}{}
ma.w.x = x
if ma.ca{{ add $i 1 }} == nil {
ma.ca{{ add $i 1 }} = &_{{ $member | TypeSymbol }}__ReprAssembler{}
}
ma.ca{{ add $i 1 }}.w = x
ma.ca{{ add $i 1 }}.m = &ma.cm
return ma.ca{{ add $i 1 }}
{{- end}}
{{- end}}
default:
panic("unreachable")
}
}
`, w, g.AdjCfg, g)
doTemplate(`
func (ma *_{{ .Type | TypeSymbol }}__ReprAssembler) Finish() error {
switch ma.state {
case maState_initial:
// carry on
case maState_midKey:
panic("invalid state: Finish cannot be called when in the middle of assembling a key")
case maState_expectValue:
panic("invalid state: Finish cannot be called when expecting start of value assembly")
case maState_midValue:
if !ma.valueFinishTidy() {
panic("invalid state: Finish cannot be called when in the middle of assembling a value")
} // if tidy success: carry on
case maState_finished:
panic("invalid state: Finish cannot be called on an assembler that's already finished")
}
if ma.ca == 0 {
return schema.ErrNotUnionStructure{TypeName:"{{ .PkgName }}.{{ .Type.Name }}.Repr", Detail: "a union must have exactly one entry (not none)!"}
}
ma.state = maState_finished
*ma.m = schema.Maybe_Value
return nil
}
`, w, g.AdjCfg, g)
doTemplate(`
func (ma *_{{ .Type | TypeSymbol }}__ReprAssembler) KeyPrototype() ipld.NodePrototype {
return _String__Prototype{}
}
func (ma *_{{ .Type | TypeSymbol }}__ReprAssembler) ValuePrototype(k string) ipld.NodePrototype {
switch k {
{{- range $i, $member := .Type.Members }}
case "{{ $member.Name }}":
return _{{ $member | TypeSymbol }}__ReprPrototype{}
{{- end}}
default:
return nil
}
}
`, w, g.AdjCfg, g)
}
func (g unionReprKeyedReprBuilderGenerator) emitKeyAssembler(w io.Writer) {
doTemplate(`
type _{{ .Type | TypeSymbol }}__ReprKeyAssembler _{{ .Type | TypeSymbol }}__ReprAssembler
`, w, g.AdjCfg, g)
stubs := mixins.StringAssemblerTraits{
g.PkgName,
g.TypeName + ".KeyAssembler", // ".Repr" is already in `g.TypeName`, so don't stutter the "Repr" part.
"_" + g.AdjCfg.TypeSymbol(g.Type) + "__ReprKey",
}
// This key assembler can disregard any idea of complex keys because we know that our discriminants are just strings!
stubs.EmitNodeAssemblerMethodBeginMap(w)
stubs.EmitNodeAssemblerMethodBeginList(w)
stubs.EmitNodeAssemblerMethodAssignNull(w)
stubs.EmitNodeAssemblerMethodAssignBool(w)
stubs.EmitNodeAssemblerMethodAssignInt(w)
stubs.EmitNodeAssemblerMethodAssignFloat(w)
doTemplate(`
func (ka *_{{ .Type | TypeSymbol }}__ReprKeyAssembler) AssignString(k string) error {
if ka.state != maState_midKey {
panic("misuse: KeyAssembler held beyond its valid lifetime")
}
if ka.ca != 0 {
return schema.ErrNotUnionStructure{TypeName:"{{ .PkgName }}.{{ .Type.Name }}.Repr", Detail: "cannot add another entry -- a union can only contain one thing!"}
}
switch k {
{{- range $i, $member := .Type.Members }}
case "{{ $member | dot.Type.RepresentationStrategy.GetDiscriminant }}":
ka.ca = {{ add $i 1 }}
{{- if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "embedAll") }}
ka.w.tag = {{ add $i 1 }}
{{- end}}
ka.state = maState_expectValue
return nil
{{- end}}
default:
return ipld.ErrInvalidKey{TypeName:"{{ .PkgName }}.{{ .Type.Name }}.Repr", Key:&_String{k}} // TODO: error quality: ErrInvalidUnionDiscriminant ?
}
return nil
}
`, w, g.AdjCfg, g)
stubs.EmitNodeAssemblerMethodAssignBytes(w)
stubs.EmitNodeAssemblerMethodAssignLink(w)
doTemplate(`
func (ka *_{{ .Type | TypeSymbol }}__ReprKeyAssembler) AssignNode(v ipld.Node) error {
if v2, err := v.AsString(); err != nil {
return err
} else {
return ka.AssignString(v2)
}
}
func (_{{ .Type | TypeSymbol }}__ReprKeyAssembler) Prototype() ipld.NodePrototype {
return _String__Prototype{}
}
`, w, g.AdjCfg, g)
}
......@@ -44,6 +44,13 @@ func Generate(pth string, pkgName string, ts schema.TypeSystem, adjCfg *AdjunctC
EmitEntireType(NewMapReprMapGenerator(pkgName, t2, adjCfg), f)
case schema.TypeList:
EmitEntireType(NewListReprListGenerator(pkgName, t2, adjCfg), f)
case schema.TypeUnion:
switch t2.RepresentationStrategy().(type) {
case schema.UnionRepresentation_Keyed:
EmitEntireType(NewUnionReprKeyedGenerator(pkgName, t2, adjCfg), f)
default:
panic("unrecognized union representation strategy")
}
default:
panic("add more type switches here :)")
}
......
package gengo
import (
"io"
)
// What's "strictoid" mean? Anything that's recursive but not infinite -- so structs, unions.
// This form of BeginMap method is reusable for anything that has fixed size and thus ignores sizehint.
// That means: structs, unions, and their relevant representations.
func emitNodeAssemblerMethodBeginMap_strictoid(w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
// We currently disregard sizeHint. It's not relevant to us.
// We could check it strictly and emit errors; presently, we don't.
// This method contains a branch to support MaybeUsesPtr because new memory may need to be allocated.
// This allocation only happens if the 'w' ptr is nil, which means we're being used on a Maybe;
// otherwise, the 'w' ptr should already be set, and we fill that memory location without allocating, as usual.
// (Note that the Maybe we're talking about here is for us, not our children: so it applies on unions too.)
doTemplate(`
func (na *_{{ .Type | TypeSymbol }}__{{ if .IsRepr }}Repr{{end}}Assembler) BeginMap(int) (ipld.MapAssembler, error) {
switch *na.m {
case schema.Maybe_Value, schema.Maybe_Null:
panic("invalid state: cannot assign into assembler that's already finished")
case midvalue:
panic("invalid state: it makes no sense to 'begin' twice on the same assembler!")
}
*na.m = midvalue
{{- if .Type | MaybeUsesPtr }}
if na.w == nil {
na.w = &_{{ .Type | TypeSymbol }}{}
}
{{- end}}
return na, nil
}
`, w, adjCfg, data)
}
......@@ -13,10 +13,23 @@ import (
func doTemplate(tmplstr string, w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
tmpl := template.Must(template.New("").
Funcs(template.FuncMap{
// These methods are used for symbol munging and appear constantly, so they need to be short.
// (You could also get at them through `.AdjCfg`, but going direct saves some screen real estate.)
"TypeSymbol": adjCfg.TypeSymbol,
"FieldSymbolLower": adjCfg.FieldSymbolLower,
"FieldSymbolUpper": adjCfg.FieldSymbolUpper,
"MaybeUsesPtr": adjCfg.MaybeUsesPtr,
// The whole AdjunctConfig can be accessed.
// Access methods like UnionMemlayout through this, as e.g. `.AdjCfg.UnionMemlayout`.
"AdjCfg": func() *AdjunctCfg { return adjCfg },
// "dot" is a dummy value that's equal to the original `.` expression, but stays there.
// Use this if you're inside a range or other feature that shifted the dot and you want the original.
// (This may seem silly, but empirically, I found myself writing a dummy line to store the value of dot before endering a range clause >20 times; that's plenty.)
"dot": func() interface{} { return data },
"KindPrim": func(k ipld.ReprKind) string {
switch k {
case ipld.ReprKind_Map:
......@@ -44,10 +57,6 @@ func doTemplate(tmplstr string, w io.Writer, adjCfg *AdjunctCfg, data interface{
"add": func(a, b int) int { return a + b },
"title": func(s string) string { return strings.Title(s) },
}).
// Seriously consider prepending `{{ $dot := . }}` (or 'top', or something).
// Or a func into the map that causes `dot` to mean `func() interface{} { return data }`.
// The number of times that the range feature has a dummy capture line above it is... not reasonable.
// (Grep for "/* ranging modifies dot, unhelpfully */" -- empirically, it's over 20 times already.)
Parse(wish.Dedent(tmplstr)))
if err := tmpl.Execute(w, data); err != nil {
panic(err)
......
package gengo
import (
"testing"
. "github.com/warpfork/go-wish"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/fluent"
"github.com/ipld/go-ipld-prime/must"
"github.com/ipld/go-ipld-prime/schema"
)
func TestUnionKeyed(t *testing.T) {
ts := schema.TypeSystem{}
ts.Init()
adjCfg := &AdjunctCfg{}
ts.Accumulate(schema.SpawnString("String"))
ts.Accumulate(schema.SpawnString("Strung"))
ts.Accumulate(schema.SpawnUnion("StrStr",
[]schema.Type{
ts.TypeByName("String"),
ts.TypeByName("Strung"),
},
schema.SpawnUnionRepresentationKeyed(map[string]schema.Type{
"a": ts.TypeByName("String"),
"b": ts.TypeByName("Strung"),
}),
))
test := func(t *testing.T, getPrototypeByName func(string) ipld.NodePrototype) {
np := getPrototypeByName("StrStr")
nrp := getPrototypeByName("StrStr.Repr")
var n schema.TypedNode
t.Run("typed-create", func(t *testing.T) {
n = fluent.MustBuildMap(np, 1, func(na fluent.MapAssembler) {
na.AssembleEntry("Strung").AssignString("whee")
}).(schema.TypedNode)
t.Run("typed-read", func(t *testing.T) {
Require(t, n.ReprKind(), ShouldEqual, ipld.ReprKind_Map)
Wish(t, n.Length(), ShouldEqual, 1)
Wish(t, must.String(must.Node(n.LookupByString("Strung"))), ShouldEqual, "whee")
})
t.Run("repr-read", func(t *testing.T) {
nr := n.Representation()
Require(t, nr.ReprKind(), ShouldEqual, ipld.ReprKind_Map)
Wish(t, nr.Length(), ShouldEqual, 1)
Wish(t, must.String(must.Node(nr.LookupByString("b"))), ShouldEqual, "whee")
})
})
t.Run("repr-create", func(t *testing.T) {
nr := fluent.MustBuildMap(nrp, 2, func(na fluent.MapAssembler) {
na.AssembleEntry("b").AssignString("whee")
})
Wish(t, n, ShouldEqual, nr)
})
}
t.Run("union-using-embed", func(t *testing.T) {
adjCfg.unionMemlayout = map[schema.TypeName]string{"StrStr": "embedAll"}
prefix := "union-keyed-using-embed"
pkgName := "main"
genAndCompileAndTest(t, prefix, pkgName, ts, adjCfg, func(t *testing.T, getPrototypeByName func(string) ipld.NodePrototype) {
test(t, getPrototypeByName)
})
})
t.Run("union-using-interface", func(t *testing.T) {
adjCfg.unionMemlayout = map[schema.TypeName]string{"StrStr": "interface"}
prefix := "union-keyed-using-interface"
pkgName := "main"
genAndCompileAndTest(t, prefix, pkgName, ts, adjCfg, func(t *testing.T, getPrototypeByName func(string) ipld.NodePrototype) {
test(t, getPrototypeByName)
})
})
}
......@@ -9,37 +9,50 @@ package schema
//
// (Meanwhile, we're using these methods in the codegen prototypes.)
// These methods use Type objects as parameters when pointing to other things,
// but this is... turning out consistently problematic.
// Even when we're doing this hacky direct-call doesn't-need-to-be-serializable temp stuff,
// as written, this doesn't actually let us express cyclic things viably!
// The same initialization questions are also going to come up again when we try to make
// concrete values in the output of codegen.
// Maybe it's actually just a bad idea to have our reified Type types use Type pointers at all.
// (I will never get tired of the tongue twisters, evidently.)
// I'm not actually using that much, and it's always avoidable (it's trivial to replace with a map lookup bouncing through a 'ts' variable somewhere).
// And having the AST gen'd types be... just... the thing... sounds nice. It could save a lot of work.
// (It would mean the golang types don't tell you whether the values have been checked for global properties or not, but, eh.)
// (It's not really compatible with "Prototype and Type are the same thing for codegen'd stuff", either (or, we need more interfaces, and to *really* lean into them), but maybe that's okay.)
func SpawnString(name TypeName) TypeString {
return TypeString{anyType{name, nil}}
return TypeString{typeBase{name, nil}}
}
func SpawnInt(name TypeName) TypeInt {
return TypeInt{anyType{name, nil}}
return TypeInt{typeBase{name, nil}}
}
func SpawnBytes(name TypeName) TypeBytes {
return TypeBytes{anyType{name, nil}}
return TypeBytes{typeBase{name, nil}}
}
func SpawnLink(name TypeName) TypeLink {
return TypeLink{anyType{name, nil}, nil, false}
return TypeLink{typeBase{name, nil}, nil, false}
}
func SpawnLinkReference(name TypeName, referenceType Type) TypeLink {
return TypeLink{anyType{name, nil}, referenceType, true}
return TypeLink{typeBase{name, nil}, referenceType, true}
}
func SpawnList(name TypeName, typ Type, nullable bool) TypeList {
return TypeList{anyType{name, nil}, false, typ, nullable}
return TypeList{typeBase{name, nil}, false, typ, nullable}
}
func SpawnMap(name TypeName, keyType Type, valueType Type, nullable bool) TypeMap {
return TypeMap{anyType{name, nil}, false, keyType, valueType, nullable}
return TypeMap{typeBase{name, nil}, false, keyType, valueType, nullable}
}
func SpawnStruct(name TypeName, fields []StructField, repr StructRepresentation) TypeStruct {
v := TypeStruct{
anyType{name, nil},
typeBase{name, nil},
fields,
make(map[string]StructField, len(fields)),
repr,
......@@ -68,6 +81,13 @@ func SpawnStructRepresentationStringjoin(delim string) StructRepresentation_Stri
return StructRepresentation_Stringjoin{delim}
}
func SpawnUnion(name TypeName, members []Type, repr UnionRepresentation) TypeUnion {
return TypeUnion{typeBase{name, nil}, members, repr}
}
func SpawnUnionRepresentationKeyed(table map[string]Type) UnionRepresentation_Keyed {
return UnionRepresentation_Keyed{table}
}
// The methods relating to TypeSystem are also mutation-heavy and placeholdery.
func (ts *TypeSystem) Init() {
......
......@@ -88,33 +88,33 @@ var (
_ Type = TypeEnum{}
)
type anyType struct {
type typeBase struct {
name TypeName
universe *TypeSystem
}
type TypeBool struct {
anyType
typeBase
}
type TypeString struct {
anyType
typeBase
}
type TypeBytes struct {
anyType
typeBase
}
type TypeInt struct {
anyType
typeBase
}
type TypeFloat struct {
anyType
typeBase
}
type TypeMap struct {
anyType
typeBase
anonymous bool
keyType Type // must be ReprKind==string (e.g. Type==String|Enum).
valueType Type
......@@ -122,39 +122,60 @@ type TypeMap struct {
}
type TypeList struct {
anyType
typeBase
anonymous bool
valueType Type
valueNullable bool
}
type TypeLink struct {
anyType
typeBase
referencedType Type
hasReferencedType bool
// ...?
}
type TypeUnion struct {
anyType
style UnionStyle
valuesKinded map[ipld.ReprKind]Type // for Style==Kinded
values map[string]Type // for Style!=Kinded (note, key is freetext, not necessarily TypeName of the value)
typeHintKey string // for Style==Envelope|Inline
contentKey string // for Style==Envelope
typeBase
// Members are listed in the order they appear in the schema.
// To find the discriminant info, you must look inside the representation; they all contain a 'table' of some kind in which the member types are the values.
// Note that multiple appearances of the same type as distinct members of the union is not possible.
// While we could do this... A: that's... odd, and nearly never called for; B: not possible with kinded mode; C: imagine the golang-native type switch! it's impossible.
// We rely on this clarity in many ways: most visibly, the type-level Node implementation for a union always uses the type names as if they were map keys! This behavior is consistent for all union representations.
members []Type
representation UnionRepresentation
}
type UnionStyle struct{ x string }
type UnionRepresentation interface{ _UnionRepresentation() }
var (
UnionStyle_Kinded = UnionStyle{"kinded"}
UnionStyle_Keyed = UnionStyle{"keyed"}
UnionStyle_Envelope = UnionStyle{"envelope"}
UnionStyle_Inline = UnionStyle{"inline"}
)
func (UnionRepresentation_Keyed) _UnionRepresentation() {}
func (UnionRepresentation_Kinded) _UnionRepresentation() {}
func (UnionRepresentation_Envelope) _UnionRepresentation() {}
func (UnionRepresentation_Inline) _UnionRepresentation() {}
// A bunch of these tables in union representation might be easier to use if flipped;
// we almost always index into them by type (since that's what we have an ordered list of);
// and they're unique in both directions, so it's equally valid either way.
// The order they're currently written in matches the serial form in the schema AST.
type UnionRepresentation_Keyed struct {
table map[string]Type // key is user-defined freetext
}
type UnionRepresentation_Kinded struct {
table map[ipld.ReprKind]Type
}
type UnionRepresentation_Envelope struct {
discriminantKey string
contentKey string
table map[string]Type // key is user-defined freetext
}
type UnionRepresentation_Inline struct {
discriminantKey string
table map[string]Type // key is user-defined freetext
}
type TypeStruct struct {
anyType
typeBase
// n.b. `Fields` is an (order-preserving!) map in the schema-schema;
// but it's a list here, with the keys denormalized into the value,
// because that's typically how we use it.
......@@ -186,7 +207,7 @@ type StructRepresentation_StringPairs struct{ sep1, sep2 string }
type StructRepresentation_Stringjoin struct{ sep string }
type TypeEnum struct {
anyType
typeBase
members []string
}
......
......@@ -2,9 +2,9 @@ package schema
/* cookie-cutter standard interface stuff */
func (anyType) _Type() {}
func (t anyType) TypeSystem() *TypeSystem { return t.universe }
func (t anyType) Name() TypeName { return t.name }
func (typeBase) _Type() {}
func (t typeBase) TypeSystem() *TypeSystem { return t.universe }
func (t typeBase) Name() TypeName { return t.name }
func (TypeBool) Kind() Kind { return Kind_Bool }
func (TypeString) Kind() Kind { return Kind_String }
......@@ -64,20 +64,26 @@ func (t TypeList) ValueIsNullable() bool {
return t.valueNullable
}
// UnionMembers returns a set of all the types that can inhabit this Union.
func (t TypeUnion) UnionMembers() map[Type]struct{} {
m := make(map[Type]struct{}, len(t.values)+len(t.valuesKinded))
switch t.style {
case UnionStyle_Kinded:
for _, v := range t.valuesKinded {
m[v] = struct{}{}
}
default:
for _, v := range t.values {
m[v] = struct{}{}
// Members returns the list of all types that are possible inhabitants of this union.
func (t TypeUnion) Members() []Type {
a := make([]Type, len(t.members))
for i := range t.members {
a[i] = t.members[i]
}
return a
}
func (t TypeUnion) RepresentationStrategy() UnionRepresentation {
return t.representation
}
func (r UnionRepresentation_Keyed) GetDiscriminant(t Type) string {
for d, t2 := range r.table {
if t2 == t {
return d
}
}
return m
panic("that type isn't a member of this union")
}
// Fields returns a slice of descriptions of the object's fields.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment