github.com/ipld/go-ipld-prime@v0.21.0/schema/gen/go/genUnion.go (about)

     1  package gengo
     2  
     3  import (
     4  	"io"
     5  
     6  	"github.com/ipld/go-ipld-prime/schema"
     7  	"github.com/ipld/go-ipld-prime/schema/gen/go/mixins"
     8  )
     9  
    10  // The generator for unions is a bit more wild than most others:
    11  // it has at three major branches for how its internals are laid out:
    12  //
    13  //   - all possible children are embedded.
    14  //   - all possible children are pointers... in which case we collapse to one interface resident.
    15  //       (n.b. this does give up some inlining potential as well as gives up on alloc amortization, but it does make resident memory size minimal.)
    16  //   - some children are emebedded and some are pointers, and of the latter set, they may be either in one interface field or several discrete pointers.
    17  //       (discrete fields of pointer type makes inlining possible in some paths, whereas an interface field blocks it).
    18  //
    19  // ... We're not doing that last one at all right now.  The pareto-prevalence of these concerns is extremely low compared to the effort required.
    20  // But the first two are both very reasonable, and both are often wanted.
    21  //
    22  // These choices are made from adjunct config (which should make sense, because they're clearly all "golang" details -- not type semantics).
    23  // We still tackle all the generation for all these strategies this in one file,
    24  //  because all of the interfaces we export are the same, regardless of the internals (and it just seems easiest to do this way).
    25  
    26  type unionGenerator struct {
    27  	AdjCfg *AdjunctCfg
    28  	mixins.MapTraits
    29  	PkgName string
    30  	Type    *schema.TypeUnion
    31  }
    32  
    33  func (unionGenerator) IsRepr() bool { return false } // hint used in some generalized templates.
    34  
    35  // --- native content and specializations --->
    36  
    37  func (g unionGenerator) EmitNativeType(w io.Writer) {
    38  	// We generate *two* types: a struct which acts as the union node,
    39  	// and also an interface which covers the members (and has an unexported marker function to make sure the set can't be extended).
    40  	//
    41  	// The interface *mostly* isn't used... except for in the return type of a speciated function which can be used to do golang-native type switches.
    42  	//
    43  	// The interface also includes a requirement for an errorless primitive access method (such as `String() string`)
    44  	// if our representation strategy is one that has that semantic (e.g., stringprefix repr does).
    45  	//
    46  	// A note about index: in all cases the index of a member type is used, we increment it by one, to avoid using zero.
    47  	// We do this because it's desirable to reserve the zero in the 'tag' field (if we generate one) as a sentinel value
    48  	// (see further comments in the EmitNodeAssemblerType function);
    49  	// and since we do it in that one case, it's just as well to do it uniformly.
    50  	doTemplate(`
    51  		{{- if Comments -}}
    52  		// {{ .Type | TypeSymbol }} matches the IPLD Schema type "{{ .Type.Name }}".
    53  		// {{ .Type | TypeSymbol }} has {{ .Type.TypeKind }} typekind, which means its data model behaviors are that of a {{ .Kind }} kind.
    54  		{{- end}}
    55  		type {{ .Type | TypeSymbol }} = *_{{ .Type | TypeSymbol }}
    56  		type _{{ .Type | TypeSymbol }} struct {
    57  			{{- if (eq (.AdjCfg.UnionMemlayout .Type) "embedAll") }}
    58  			tag uint
    59  			{{- range $i, $member := .Type.Members }}
    60  			x{{ add $i 1 }} _{{ $member | TypeSymbol }}
    61  			{{- end}}
    62  			{{- else if (eq (.AdjCfg.UnionMemlayout .Type) "interface") }}
    63  			x _{{ .Type | TypeSymbol }}__iface
    64  			{{- end}}
    65  		}
    66  		type _{{ .Type | TypeSymbol }}__iface interface {
    67  			_{{ .Type | TypeSymbol }}__member()
    68  			{{- if (eq (.Type.RepresentationStrategy | printf "%T") "schema.UnionRepresentation_Stringprefix") }}
    69  			String() string
    70  			{{- end}}
    71  		}
    72  
    73  		{{- range $member := .Type.Members }}
    74  		func (_{{ $member | TypeSymbol }}) _{{ dot.Type | TypeSymbol }}__member() {}
    75  		{{- end}}
    76  	`, w, g.AdjCfg, g)
    77  }
    78  
    79  func (g unionGenerator) EmitNativeAccessors(w io.Writer) {
    80  	doTemplate(`
    81  		func (n _{{ .Type | TypeSymbol }}) AsInterface() _{{ .Type | TypeSymbol }}__iface {
    82  			{{- if (eq (.AdjCfg.UnionMemlayout .Type) "embedAll") }}
    83  			switch n.tag {
    84  			{{- range $i, $member := .Type.Members }}
    85  			case {{ add $i 1 }}:
    86  				return &n.x{{ add $i 1 }}
    87  			{{- end}}
    88  			default:
    89  				panic("invalid union state; how did you create this object?")
    90  			}
    91  			{{- else if (eq (.AdjCfg.UnionMemlayout .Type) "interface") }}
    92  			return n.x
    93  			{{- end}}
    94  		}
    95  	`, w, g.AdjCfg, g)
    96  }
    97  
    98  func (g unionGenerator) EmitNativeBuilder(w io.Writer) {
    99  	// Unclear as yet what should go here.
   100  }
   101  
   102  func (g unionGenerator) EmitNativeMaybe(w io.Writer) {
   103  	emitNativeMaybe(w, g.AdjCfg, g)
   104  }
   105  
   106  // --- type info --->
   107  
   108  func (g unionGenerator) EmitTypeConst(w io.Writer) {
   109  	doTemplate(`
   110  		// TODO EmitTypeConst
   111  	`, w, g.AdjCfg, g)
   112  }
   113  
   114  // --- TypedNode interface satisfaction --->
   115  
   116  func (g unionGenerator) EmitTypedNodeMethodType(w io.Writer) {
   117  	doTemplate(`
   118  		func ({{ .Type | TypeSymbol }}) Type() schema.Type {
   119  			return nil /*TODO:typelit*/
   120  		}
   121  	`, w, g.AdjCfg, g)
   122  }
   123  
   124  func (g unionGenerator) EmitTypedNodeMethodRepresentation(w io.Writer) {
   125  	emitTypicalTypedNodeMethodRepresentation(w, g.AdjCfg, g)
   126  }
   127  
   128  // --- Node interface satisfaction --->
   129  
   130  func (g unionGenerator) EmitNodeType(w io.Writer) {
   131  	// No additional types needed.  Methods all attach to the native type.
   132  
   133  	// We do, however, want some constants for our member names;
   134  	//  they'll make iterators able to work faster.  So let's emit those.
   135  	// These are a bit perplexing, because they're... type names.
   136  	//  However, oddly enough, we don't have type names available *as nodes* anywhere else centrally available,
   137  	//   so... we generate some values for them here with scoped identifers and get on with it.
   138  	//    Maybe this could be elided with future work.
   139  	doTemplate(`
   140  		var (
   141  			{{- range $member := .Type.Members }}
   142  			memberName__{{ dot.Type | TypeSymbol }}_{{ $member.Name }} = _String{"{{ $member.Name }}"}
   143  			{{- end }}
   144  		)
   145  	`, w, g.AdjCfg, g)
   146  }
   147  
   148  func (g unionGenerator) EmitNodeTypeAssertions(w io.Writer) {
   149  	emitNodeTypeAssertions_typical(w, g.AdjCfg, g)
   150  }
   151  
   152  func (g unionGenerator) EmitNodeMethodLookupByString(w io.Writer) {
   153  	doTemplate(`
   154  		func (n {{ .Type | TypeSymbol }}) LookupByString(key string) (datamodel.Node, error) {
   155  			switch key {
   156  			{{- range $i, $member := .Type.Members }}
   157  			case "{{ $member.Name }}":
   158  				{{- if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "embedAll") }}
   159  				if n.tag != {{ add $i 1 }} {
   160  					return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfString(key)}
   161  				}
   162  				return &n.x{{ add $i 1 }}, nil
   163  				{{- else if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "interface") }}
   164  				if n2, ok := n.x.({{ $member | TypeSymbol }}); ok {
   165  					return n2, nil
   166  				} else {
   167  					return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfString(key)}
   168  				}
   169  				{{- end}}
   170  			{{- end}}
   171  			default:
   172  				return nil, schema.ErrNoSuchField{Type: nil /*TODO*/, Field: datamodel.PathSegmentOfString(key)}
   173  			}
   174  		}
   175  	`, w, g.AdjCfg, g)
   176  }
   177  
   178  func (g unionGenerator) EmitNodeMethodLookupByNode(w io.Writer) {
   179  	doTemplate(`
   180  		func (n {{ .Type | TypeSymbol }}) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
   181  			ks, err := key.AsString()
   182  			if err != nil {
   183  				return nil, err
   184  			}
   185  			return n.LookupByString(ks)
   186  		}
   187  	`, w, g.AdjCfg, g)
   188  }
   189  
   190  func (g unionGenerator) EmitNodeMethodMapIterator(w io.Writer) {
   191  	// This is kind of a hilarious "iterator": it has to count all the way up to... 1.
   192  	doTemplate(`
   193  		func (n {{ .Type | TypeSymbol }}) MapIterator() datamodel.MapIterator {
   194  			return &_{{ .Type | TypeSymbol }}__MapItr{n, false}
   195  		}
   196  
   197  		type _{{ .Type | TypeSymbol }}__MapItr struct {
   198  			n {{ .Type | TypeSymbol }}
   199  			done bool
   200  		}
   201  
   202  		func (itr *_{{ .Type | TypeSymbol }}__MapItr) Next() (k datamodel.Node, v datamodel.Node, _ error) {
   203  			if itr.done {
   204  				return nil, nil, datamodel.ErrIteratorOverread{}
   205  			}
   206  			{{- if (eq (.AdjCfg.UnionMemlayout .Type) "embedAll") }}
   207  			switch itr.n.tag {
   208  			{{- range $i, $member := .Type.Members }}
   209  			case {{ add $i 1 }}:
   210  				k, v = &memberName__{{ dot.Type | TypeSymbol }}_{{ $member.Name }}, &itr.n.x{{ add $i 1 }}
   211  			{{- end}}
   212  			{{- else if (eq (.AdjCfg.UnionMemlayout .Type) "interface") }}
   213  			switch n2 := itr.n.x.(type) {
   214  			{{- range $member := .Type.Members }}
   215  			case {{ $member | TypeSymbol }}:
   216  				k, v = &memberName__{{ dot.Type | TypeSymbol }}_{{ $member.Name }}, n2
   217  			{{- end}}
   218  			{{- end}}
   219  			default:
   220  				panic("unreachable")
   221  			}
   222  			itr.done = true
   223  			return
   224  		}
   225  		func (itr *_{{ .Type | TypeSymbol }}__MapItr) Done() bool {
   226  			return itr.done
   227  		}
   228  
   229  	`, w, g.AdjCfg, g)
   230  }
   231  
   232  func (g unionGenerator) EmitNodeMethodLength(w io.Writer) {
   233  	doTemplate(`
   234  		func ({{ .Type | TypeSymbol }}) Length() int64 {
   235  			return 1
   236  		}
   237  	`, w, g.AdjCfg, g)
   238  }
   239  
   240  func (g unionGenerator) EmitNodeMethodPrototype(w io.Writer) {
   241  	emitNodeMethodPrototype_typical(w, g.AdjCfg, g)
   242  }
   243  
   244  func (g unionGenerator) EmitNodePrototypeType(w io.Writer) {
   245  	emitNodePrototypeType_typical(w, g.AdjCfg, g)
   246  }
   247  
   248  // --- NodeBuilder and NodeAssembler --->
   249  
   250  func (g unionGenerator) GetNodeBuilderGenerator() NodeBuilderGenerator {
   251  	return unionBuilderGenerator{
   252  		g.AdjCfg,
   253  		mixins.MapAssemblerTraits{
   254  			PkgName:       g.PkgName,
   255  			TypeName:      g.TypeName,
   256  			AppliedPrefix: "_" + g.AdjCfg.TypeSymbol(g.Type) + "__",
   257  		},
   258  		g.PkgName,
   259  		g.Type,
   260  	}
   261  }
   262  
   263  type unionBuilderGenerator struct {
   264  	AdjCfg *AdjunctCfg
   265  	mixins.MapAssemblerTraits
   266  	PkgName string
   267  	Type    *schema.TypeUnion
   268  }
   269  
   270  func (unionBuilderGenerator) IsRepr() bool { return false } // hint used in some generalized templates.
   271  
   272  func (g unionBuilderGenerator) EmitNodeBuilderType(w io.Writer) {
   273  	emitEmitNodeBuilderType_typical(w, g.AdjCfg, g)
   274  }
   275  func (g unionBuilderGenerator) EmitNodeBuilderMethods(w io.Writer) {
   276  	emitNodeBuilderMethods_typical(w, g.AdjCfg, g)
   277  }
   278  func (g unionBuilderGenerator) EmitNodeAssemblerType(w io.Writer) {
   279  	// Assemblers for unions are not unlikely those for structs or maps:
   280  	//
   281  	// - 'w' is the "**w**ip" pointer.
   282  	// - 'm' is the pointer to a **m**aybe which communicates our completeness to the parent if we're a child assembler.
   283  	//     Like any other structure, a union can be nullable in the context of some enclosing object, and we'll have the usual branches for handling that in our various Assign methods.
   284  	// - 'state' is what it says on the tin.  Unions use maState to sequence the transitions between a new assembler, the map having been started, key insertions, value insertions, and finish.
   285  	//     Most of this is just like the way struct and map use maState.
   286  	//     However, we also need to guard to make sure a second entry never begins; after the first, finish is the *only* valid transition.
   287  	//     In structs, this is done using the "set" bitfield; in maps, the state resides in the wip map itself.
   288  	//     Unions are more like the latter: depending on which memory layout we're using, either the `na.w.tag` value, or, a non-nil `na.w.x`, is indicative that one key has been entered.
   289  	//     (The zero value for `na.w.tag` is reserved, and all  for this reason.
   290  	// - There is no additional state need to store "focus" (in contrast to structs);
   291  	//     information during the AssembleValue phase about which member is selected is also just handled in `na.w.tag`, or, in the type info of `na.w.x`, again depending on memory layout strategy.
   292  	//     (This is subverted a bit by the 'ca' field, however... which effectively mirrors `na.w.tag`, and is only active in the resetting process, but is necessary because it outlives its twin inside 'w'.)
   293  	//
   294  	// - 'cm' is **c**hild **m**aybe and is used for the completion message from children.
   295  	// - 'ca*' fields embed **c**hild **a**ssemblers -- these are embedded so we can yield pointers to them during recusion into child value assembly without causing new allocations.
   296  	//     In unions, only one of these will every be used!  However, we don't know *which one* in advance, so, we have to embed them all.
   297  	//     (It's ironic to note that if the golang compiler had an understanding of unions itself (either tagged or untagged would suffice), we could compile this down into *much* more minimal amounts of resident memory reservation.  Alas!)
   298  	//     The 'ca*' fields are pointers (and allocated on demand) instead of embeds for unions with memlayout=interface mode.  (Arguably, this is overloading that config; PRs for more granular configurability welcome.)
   299  	// - 'ca' (with no further suffix) identifies which child assembler was previously used.
   300  	//     This is for minimizing the amount of work that resetting has to do: it will only recurse into resetting that child assembler.
   301  	doTemplate(`
   302  		type _{{ .Type | TypeSymbol }}__Assembler struct {
   303  			w *_{{ .Type | TypeSymbol }}
   304  			m *schema.Maybe
   305  			state maState
   306  
   307  			cm schema.Maybe
   308  			{{- range $i, $member := .Type.Members }}
   309  			ca{{ add $i 1 }} {{ if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "interface") }}*{{end}}_{{ $member | TypeSymbol }}__Assembler
   310  			{{end -}}
   311  			ca uint
   312  		}
   313  	`, w, g.AdjCfg, g)
   314  
   315  	// Reset methods for unions are a tad more involved than for most other assemblers:
   316  	//  we only want to bother to reset whichever child assembler (if any) we actually used last.
   317  	//  We *could* blithely reset *all* child assemblers every time; but, trading an extra bit of state in our assembler
   318  	//   for the privledge of trimming off a potentially sizable amount of unnecessary zeroing efforts seems preferrable.
   319  	//  Also, although go syntax makes it not textually obvious here, note that it's possible for the child assemblers to be either pointers or embeds:
   320  	//   on consequence of this is that just zeroing this struct would be both unreliable and undesirable in the pointer case
   321  	//    (it would leave orphan child assemblers that might still have pointers into us, which could be guarded against but is nonetheless is considerably scary in complexity;
   322  	//    and it would also mean that we can't keep ahold of the child assemblers across resets and thus amortize allocations, which... is the whole reason the reset system exists in the first place).
   323  	doTemplate(`
   324  		func (na *_{{ .Type | TypeSymbol }}__Assembler) reset() {
   325  			na.state = maState_initial
   326  			switch na.ca {
   327  			case 0:
   328  				return
   329  			{{- range $i, $member := .Type.Members }}
   330  			case {{ add $i 1 }}:
   331  				na.ca{{ add $i 1 }}.reset()
   332  			{{end -}}
   333  			default:
   334  				panic("unreachable")
   335  			}
   336  			na.ca = 0
   337  			na.cm = schema.Maybe_Absent
   338  		}
   339  	`, w, g.AdjCfg, g)
   340  }
   341  func (g unionBuilderGenerator) EmitNodeAssemblerMethodBeginMap(w io.Writer) {
   342  	emitNodeAssemblerMethodBeginMap_strictoid(w, g.AdjCfg, g)
   343  }
   344  func (g unionBuilderGenerator) EmitNodeAssemblerMethodAssignNull(w io.Writer) {
   345  	// It might sound a bit odd to call a union "recursive", since it's so very trivially so (no fan-out),
   346  	//  but it's functionally accurate: the generated method should include a branch for the 'midvalue' state.
   347  	emitNodeAssemblerMethodAssignNull_recursive(w, g.AdjCfg, g)
   348  }
   349  func (g unionBuilderGenerator) EmitNodeAssemblerMethodAssignNode(w io.Writer) {
   350  	// AssignNode goes through three phases:
   351  	// 1. is it null?  Jump over to AssignNull (which may or may not reject it).
   352  	// 2. is it our own type?  Handle specially -- we might be able to do efficient things.
   353  	// 3. is it the right kind to morph into us?  Do so.
   354  	//
   355  	// We do not set m=midvalue in phase 3 -- it shouldn't matter unless you're trying to pull off concurrent access, which is wrong and unsafe regardless.
   356  	//
   357  	// DRY: this turns out to be textually identical to the method for structs!  (At least, for now.  It could/should probably be optimized to get to the point faster in phase 3.)
   358  	doTemplate(`
   359  		func (na *_{{ .Type | TypeSymbol }}__Assembler) AssignNode(v datamodel.Node) error {
   360  			if v.IsNull() {
   361  				return na.AssignNull()
   362  			}
   363  			if v2, ok := v.(*_{{ .Type | TypeSymbol }}); ok {
   364  				switch *na.m {
   365  				case schema.Maybe_Value, schema.Maybe_Null:
   366  					panic("invalid state: cannot assign into assembler that's already finished")
   367  				case midvalue:
   368  					panic("invalid state: cannot assign null into an assembler that's already begun working on recursive structures!")
   369  				}
   370  				{{- if .Type | MaybeUsesPtr }}
   371  				if na.w == nil {
   372  					na.w = v2
   373  					*na.m = schema.Maybe_Value
   374  					return nil
   375  				}
   376  				{{- end}}
   377  				*na.w = *v2
   378  				*na.m = schema.Maybe_Value
   379  				return nil
   380  			}
   381  			if v.Kind() != datamodel.Kind_Map {
   382  				return datamodel.ErrWrongKind{TypeName: "{{ .PkgName }}.{{ .Type.Name }}", MethodName: "AssignNode", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: v.Kind()}
   383  			}
   384  			itr := v.MapIterator()
   385  			for !itr.Done() {
   386  				k, v, err := itr.Next()
   387  				if err != nil {
   388  					return err
   389  				}
   390  				if err := na.AssembleKey().AssignNode(k); err != nil {
   391  					return err
   392  				}
   393  				if err := na.AssembleValue().AssignNode(v); err != nil {
   394  					return err
   395  				}
   396  			}
   397  			return na.Finish()
   398  		}
   399  	`, w, g.AdjCfg, g)
   400  }
   401  func (g unionBuilderGenerator) EmitNodeAssemblerOtherBits(w io.Writer) {
   402  	g.emitMapAssemblerChildTidyHelper(w)
   403  	g.emitMapAssemblerMethods(w)
   404  	g.emitKeyAssembler(w)
   405  }
   406  func (g unionBuilderGenerator) emitMapAssemblerChildTidyHelper(w io.Writer) {
   407  	// This function attempts to clean up the state machine to acknolwedge child assembly finish.
   408  	//  If the child was finished and we just collected it, return true and update state to maState_initial.
   409  	//  Otherwise, if it wasn't done, return false;
   410  	//   and the caller is almost certain to emit an error momentarily.
   411  	// The function will only be called when the current state is maState_midValue.
   412  	//  (In general, the idea is that if the user is doing things correctly,
   413  	//   this function will only be called when the child is in fact finished.)
   414  	// This is a *lot* simpler than the tidy behaviors needed for any of the other recursive kinds:
   415  	//  unions don't allow either nullable nor optional members, so there's no need to process anything except Maybe_Value state,
   416  	//  and the lack of need to consider nullable nor optionals also means we never need to worry about moving memory in the case of MaybeUsePtr modes.
   417  	//  (FUTURE: this may get a bit more conditional if we support members that are of unit ype and have null as a representation.  Unsure how that would work out exactly, but should be possible.)
   418  	// We don't bother to nil the child assembler's 'w' pointer: it's not necessary,
   419  	//  because we'll never "share" 'cm' (as some systems, like maps and lists, do) or change its value (short of the whole assembler resetting),
   420  	//   and therefore we should be able to rely on the child assembler to be reasonable and never start acting again after finish.
   421  	//  (This *does* mean some care is required in the reset logic: we have to be absolutely sure that resetting propagates to all child assemblers,
   422  	//   even if they're in other regions of the heap; otherwise, they might end up still holding actionable 'w' and 'm' pointers into bad times!)
   423  	//  (If you want to compare this to the logic in struct assemblers: it's similar to how only children that don't have maybes need an active 'w' nil'ing;
   424  	//   but the salient reason there isn't "because the don't have maybes"; it's "because they have a potentially-reused 'cm'".  We don't have the former; but we *also* don't have the latter, for other reasons.)
   425  	doTemplate(`
   426  		func (ma *_{{ .Type | TypeSymbol }}__Assembler) valueFinishTidy() bool {
   427  			switch ma.cm {
   428  			case schema.Maybe_Value:
   429  				{{- /* nothing to do for memlayout=embedAll; the tag is already set and memory already in place. */ -}}
   430  				{{- /* nothing to do for memlayout=interface either; same story, the values are already in place. */ -}}
   431  				ma.state = maState_initial
   432  				return true
   433  			default:
   434  				return false
   435  			}
   436  		}
   437  	`, w, g.AdjCfg, g)
   438  }
   439  func (g unionBuilderGenerator) emitMapAssemblerMethods(w io.Writer) {
   440  	// DRY: I did an interesting thing here: the `switch ma.state` block remains textually identical to the one for structs,
   441  	//  even though the branch by valueFinishTidy could jump directly to an error state.
   442  	//   That same semantic error state gets checked separately a few lines later in a different mechanism.
   443  	//    The later check is needed either way (the assembler needs to *keep* erroring if some derp calls AssembleEntry *again* after a previous call already did the tidy and got rejected),
   444  	//     but we could arguably save a step there.  It would probably trade more assembly size for the cycles saved, too, though.
   445  	//  Ah, tradeoffs.  I think the textually simple approach here is probably in fact the best.  But it could be done differently, yes.
   446  	// Note that calling AssembleEntry again when it's not for the first entry *returns* an error; it doesn't panic.
   447  	//  This is subtle but important: trying to add more data than is acceptable is a data mismatch, not a system misuse, and must error accordingly politely.
   448  	doTemplate(`
   449  		func (ma *_{{ .Type | TypeSymbol }}__Assembler) AssembleEntry(k string) (datamodel.NodeAssembler, error) {
   450  			switch ma.state {
   451  			case maState_initial:
   452  				// carry on
   453  			case maState_midKey:
   454  				panic("invalid state: AssembleEntry cannot be called when in the middle of assembling another key")
   455  			case maState_expectValue:
   456  				panic("invalid state: AssembleEntry cannot be called when expecting start of value assembly")
   457  			case maState_midValue:
   458  				if !ma.valueFinishTidy() {
   459  					panic("invalid state: AssembleEntry cannot be called when in the middle of assembling a value")
   460  				} // if tidy success: carry on for the moment, but we'll still be erroring shortly.
   461  			case maState_finished:
   462  				panic("invalid state: AssembleEntry cannot be called on an assembler that's already finished")
   463  			}
   464  			if ma.ca != 0 {
   465  				return nil, schema.ErrNotUnionStructure{TypeName:"{{ .PkgName }}.{{ .Type.Name }}", Detail: "cannot add another entry -- a union can only contain one thing!"}
   466  			}
   467  			{{- if .Type.Members }}
   468  			switch k {
   469  			{{- range $i, $member := .Type.Members }}
   470  			case "{{ $member.Name }}":
   471  				ma.state = maState_midValue
   472  				ma.ca = {{ add $i 1 }}
   473  				{{- if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "embedAll") }}
   474  				ma.w.tag = {{ add $i 1 }}
   475  				ma.ca{{ add $i 1 }}.w = &ma.w.x{{ add $i 1 }}
   476  				ma.ca{{ add $i 1 }}.m = &ma.cm
   477  				return &ma.ca{{ add $i 1 }}, nil
   478  				{{- else if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "interface") }}
   479  				x := &_{{ $member | TypeSymbol}}{}
   480  				ma.w.x = x
   481  				if ma.ca{{ add $i 1 }} == nil {
   482  					ma.ca{{ add $i 1 }} = &_{{ $member | TypeSymbol }}__Assembler{}
   483  				}
   484  				ma.ca{{ add $i 1 }}.w = x
   485  				ma.ca{{ add $i 1 }}.m = &ma.cm
   486  				return ma.ca{{ add $i 1 }}, nil
   487  				{{- end}}
   488  			{{- end}}
   489  			{{- end}}
   490  			}
   491  			return nil, schema.ErrInvalidKey{TypeName:"{{ .PkgName }}.{{ .Type.Name }}", Key:&_String{k}}
   492  		}
   493  	`, w, g.AdjCfg, g)
   494  
   495  	// AssembleKey has a similar DRY note as the AssembleEntry above had.
   496  	// One misfortune in this method: we may know that we're doomed to errors because the caller is trying to start a second entry,
   497  	//  but we can't report it from this method: we have to sit on our tongue, slide to midKey state (even though we're doomed!),
   498  	//   and let the keyAssembler return the error later.
   499  	//    This sucks, but panicking wouldn't be correct (see remarks about error vs panic on the AssembleEntry method),
   500  	//     and we don't want to make this call unchainable for everyone everywhere, either, so it can't be rewritten to have an immmediate error return.
   501  	//    The transition to midKey state is particularly irritating because it means this assembler will be perma-wedged; but I see no alternative.
   502  	doTemplate(`
   503  		func (ma *_{{ .Type | TypeSymbol }}__Assembler) AssembleKey() datamodel.NodeAssembler {
   504  			switch ma.state {
   505  			case maState_initial:
   506  				// carry on
   507  			case maState_midKey:
   508  				panic("invalid state: AssembleKey cannot be called when in the middle of assembling another key")
   509  			case maState_expectValue:
   510  				panic("invalid state: AssembleKey cannot be called when expecting start of value assembly")
   511  			case maState_midValue:
   512  				if !ma.valueFinishTidy() {
   513  					panic("invalid state: AssembleKey cannot be called when in the middle of assembling a value")
   514  				} // if tidy success: carry on for the moment, but we'll still be erroring shortly... or rather, the keyassembler will be.
   515  			case maState_finished:
   516  				panic("invalid state: AssembleKey cannot be called on an assembler that's already finished")
   517  			}
   518  			ma.state = maState_midKey
   519  			return (*_{{ .Type | TypeSymbol }}__KeyAssembler)(ma)
   520  		}
   521  	`, w, g.AdjCfg, g)
   522  
   523  	// As with structs, the responsibilties of this are similar to AssembleEntry, but with some of the burden split into the key assembler (which should have acted earlier),
   524  	//  and some of the logical continuity bounces through state in the form of 'ma.ca'.
   525  	//  The potential to DRY up some of this should be plentiful, but it's a bit heady.
   526  	doTemplate(`
   527  		func (ma *_{{ .Type | TypeSymbol }}__Assembler) AssembleValue() datamodel.NodeAssembler {
   528  			switch ma.state {
   529  			case maState_initial:
   530  				panic("invalid state: AssembleValue cannot be called when no key is primed")
   531  			case maState_midKey:
   532  				panic("invalid state: AssembleValue cannot be called when in the middle of assembling a key")
   533  			case maState_expectValue:
   534  				// carry on
   535  			case maState_midValue:
   536  				panic("invalid state: AssembleValue cannot be called when in the middle of assembling another value")
   537  			case maState_finished:
   538  				panic("invalid state: AssembleValue cannot be called on an assembler that's already finished")
   539  			}
   540  			ma.state = maState_midValue
   541  			switch ma.ca {
   542  			{{- range $i, $member := .Type.Members }}
   543  			case {{ add $i 1 }}:
   544  				{{- if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "embedAll") }}
   545  				ma.ca{{ add $i 1 }}.w = &ma.w.x{{ add $i 1 }}
   546  				ma.ca{{ add $i 1 }}.m = &ma.cm
   547  				return &ma.ca{{ add $i 1 }}
   548  				{{- else if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "interface") }}
   549  				x := &_{{ $member | TypeSymbol}}{}
   550  				ma.w.x = x
   551  				if ma.ca{{ add $i 1 }} == nil {
   552  					ma.ca{{ add $i 1 }} = &_{{ $member | TypeSymbol }}__Assembler{}
   553  				}
   554  				ma.ca{{ add $i 1 }}.w = x
   555  				ma.ca{{ add $i 1 }}.m = &ma.cm
   556  				return ma.ca{{ add $i 1 }}
   557  				{{- end}}
   558  			{{- end}}
   559  			default:
   560  				panic("unreachable")
   561  			}
   562  		}
   563  	`, w, g.AdjCfg, g)
   564  
   565  	// Finish checks are nice and easy.  Is the maState in the right place now and was a 'ca' ever marked?
   566  	//  If yes and yes, then together with the rules elsewhere, we must've processed and accepted exactly one entry; perfect.
   567  	doTemplate(`
   568  		func (ma *_{{ .Type | TypeSymbol }}__Assembler) Finish() error {
   569  			switch ma.state {
   570  			case maState_initial:
   571  				// carry on
   572  			case maState_midKey:
   573  				panic("invalid state: Finish cannot be called when in the middle of assembling a key")
   574  			case maState_expectValue:
   575  				panic("invalid state: Finish cannot be called when expecting start of value assembly")
   576  			case maState_midValue:
   577  				if !ma.valueFinishTidy() {
   578  					panic("invalid state: Finish cannot be called when in the middle of assembling a value")
   579  				} // if tidy success: carry on
   580  			case maState_finished:
   581  				panic("invalid state: Finish cannot be called on an assembler that's already finished")
   582  			}
   583  			if ma.ca == 0 {
   584  				return schema.ErrNotUnionStructure{TypeName:"{{ .PkgName }}.{{ .Type.Name }}", Detail: "a union must have exactly one entry (not none)!"}
   585  			}
   586  			ma.state = maState_finished
   587  			*ma.m = schema.Maybe_Value
   588  			return nil
   589  		}
   590  	`, w, g.AdjCfg, g)
   591  
   592  	doTemplate(`
   593  		func (ma *_{{ .Type | TypeSymbol }}__Assembler) KeyPrototype() datamodel.NodePrototype {
   594  			return _String__Prototype{}
   595  		}
   596  		func (ma *_{{ .Type | TypeSymbol }}__Assembler) ValuePrototype(k string) datamodel.NodePrototype {
   597  			switch k {
   598  			{{- range $i, $member := .Type.Members }}
   599  			case "{{ $member.Name }}":
   600  				return _{{ $member | TypeSymbol }}__Prototype{}
   601  			{{- end}}
   602  			default:
   603  				return nil
   604  			}
   605  		}
   606  	`, w, g.AdjCfg, g)
   607  }
   608  func (g unionBuilderGenerator) emitKeyAssembler(w io.Writer) {
   609  	doTemplate(`
   610  		type _{{ .Type | TypeSymbol }}__KeyAssembler _{{ .Type | TypeSymbol }}__Assembler
   611  	`, w, g.AdjCfg, g)
   612  	stubs := mixins.StringAssemblerTraits{
   613  		PkgName:       g.PkgName,
   614  		TypeName:      g.TypeName + ".KeyAssembler",
   615  		AppliedPrefix: "_" + g.AdjCfg.TypeSymbol(g.Type) + "__Key",
   616  	}
   617  	// This key assembler can disregard any idea of complex keys because we're fronting for a union!
   618  	//  Union member names must be strings (and quite simple ones at that).
   619  	stubs.EmitNodeAssemblerMethodBeginMap(w)
   620  	stubs.EmitNodeAssemblerMethodBeginList(w)
   621  	stubs.EmitNodeAssemblerMethodAssignNull(w)
   622  	stubs.EmitNodeAssemblerMethodAssignBool(w)
   623  	stubs.EmitNodeAssemblerMethodAssignInt(w)
   624  	stubs.EmitNodeAssemblerMethodAssignFloat(w)
   625  	doTemplate(`
   626  		func (ka *_{{ .Type | TypeSymbol }}__KeyAssembler) AssignString(k string) error {
   627  			if ka.state != maState_midKey {
   628  				panic("misuse: KeyAssembler held beyond its valid lifetime")
   629  			}
   630  			if ka.ca != 0 {
   631  				return schema.ErrNotUnionStructure{TypeName:"{{ .PkgName }}.{{ .Type.Name }}", Detail: "cannot add another entry -- a union can only contain one thing!"}
   632  			}
   633  			switch k {
   634  			{{- range $i, $member := .Type.Members }}
   635  			case "{{ $member.Name }}":
   636  				ka.ca = {{ add $i 1 }}
   637  				{{- if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "embedAll") }}
   638  				ka.w.tag = {{ add $i 1 }}
   639  				{{- end}}
   640  				ka.state = maState_expectValue
   641  				return nil
   642  			{{- end}}
   643  			}
   644  			return schema.ErrInvalidKey{TypeName:"{{ .PkgName }}.{{ .Type.Name }}", Key:&_String{k}} // TODO: error quality: ErrInvalidUnionDiscriminant ?
   645  		}
   646  	`, w, g.AdjCfg, g)
   647  	stubs.EmitNodeAssemblerMethodAssignBytes(w)
   648  	stubs.EmitNodeAssemblerMethodAssignLink(w)
   649  	doTemplate(`
   650  		func (ka *_{{ .Type | TypeSymbol }}__KeyAssembler) AssignNode(v datamodel.Node) error {
   651  			if v2, err := v.AsString(); err != nil {
   652  				return err
   653  			} else {
   654  				return ka.AssignString(v2)
   655  			}
   656  		}
   657  		func (_{{ .Type | TypeSymbol }}__KeyAssembler) Prototype() datamodel.NodePrototype {
   658  			return _String__Prototype{}
   659  		}
   660  	`, w, g.AdjCfg, g)
   661  }