github.com/ipld/go-ipld-prime@v0.21.0/schema/gen/go/genStruct.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  type structGenerator struct {
    11  	AdjCfg *AdjunctCfg
    12  	mixins.MapTraits
    13  	PkgName string
    14  	Type    *schema.TypeStruct
    15  }
    16  
    17  func (structGenerator) IsRepr() bool { return false } // hint used in some generalized templates.
    18  
    19  // --- native content and specializations --->
    20  
    21  func (g structGenerator) EmitNativeType(w io.Writer) {
    22  	doTemplate(`
    23  		{{- if Comments -}}
    24  		// {{ .Type | TypeSymbol }} matches the IPLD Schema type "{{ .Type.Name }}".  It has {{ .Type.TypeKind }} type-kind, and may be interrogated like {{ .Kind }} kind.
    25  		{{- end}}
    26  		type {{ .Type | TypeSymbol }} = *_{{ .Type | TypeSymbol }}
    27  		type _{{ .Type | TypeSymbol }} struct {
    28  			{{- range $field := .Type.Fields}}
    29  			{{ $field | FieldSymbolLower }} _{{ $field.Type | TypeSymbol }}{{if $field.IsMaybe }}__Maybe{{end}}
    30  			{{- end}}
    31  		}
    32  	`, w, g.AdjCfg, g)
    33  }
    34  
    35  func (g structGenerator) EmitNativeAccessors(w io.Writer) {
    36  	doTemplate(`
    37  		{{- $type := .Type -}} {{- /* ranging modifies dot, unhelpfully */ -}}
    38  		{{- range $field := .Type.Fields }}
    39  		func (n _{{ $type | TypeSymbol }}) Field{{ $field | FieldSymbolUpper }}() {{ if $field.IsMaybe }}Maybe{{end}}{{ $field.Type | TypeSymbol }} {
    40  			return &n.{{ $field | FieldSymbolLower }}
    41  		}
    42  		{{- end}}
    43  	`, w, g.AdjCfg, g)
    44  }
    45  
    46  func (g structGenerator) EmitNativeBuilder(w io.Writer) {
    47  	// Unclear what, if anything, goes here.
    48  }
    49  
    50  func (g structGenerator) EmitNativeMaybe(w io.Writer) {
    51  	emitNativeMaybe(w, g.AdjCfg, g)
    52  }
    53  
    54  // --- type info --->
    55  
    56  func (g structGenerator) EmitTypeConst(w io.Writer) {
    57  	doTemplate(`
    58  		// TODO EmitTypeConst
    59  	`, w, g.AdjCfg, g)
    60  }
    61  
    62  // --- TypedNode interface satisfaction --->
    63  
    64  func (g structGenerator) EmitTypedNodeMethodType(w io.Writer) {
    65  	doTemplate(`
    66  		func ({{ .Type | TypeSymbol }}) Type() schema.Type {
    67  			return nil /*TODO:typelit*/
    68  		}
    69  	`, w, g.AdjCfg, g)
    70  }
    71  
    72  func (g structGenerator) EmitTypedNodeMethodRepresentation(w io.Writer) {
    73  	emitTypicalTypedNodeMethodRepresentation(w, g.AdjCfg, g)
    74  }
    75  
    76  // --- Node interface satisfaction --->
    77  
    78  func (g structGenerator) EmitNodeType(w io.Writer) {
    79  	// No additional types needed.  Methods all attach to the native type.
    80  	// We do, however, want some constants for our fields;
    81  	//  they'll make iterators able to work faster.  So let's emit those.
    82  	doTemplate(`
    83  		var (
    84  			{{- $type := .Type -}} {{- /* ranging modifies dot, unhelpfully */ -}}
    85  			{{- range $field := .Type.Fields }}
    86  			fieldName__{{ $type | TypeSymbol }}_{{ $field | FieldSymbolUpper }} = _String{"{{ $field.Name }}"}
    87  			{{- end }}
    88  		)
    89  	`, w, g.AdjCfg, g)
    90  }
    91  
    92  func (g structGenerator) EmitNodeTypeAssertions(w io.Writer) {
    93  	emitNodeTypeAssertions_typical(w, g.AdjCfg, g)
    94  }
    95  
    96  func (g structGenerator) EmitNodeMethodLookupByString(w io.Writer) {
    97  	doTemplate(`
    98  		func (n {{ .Type | TypeSymbol }}) LookupByString(key string) (datamodel.Node, error) {
    99  			switch key {
   100  			{{- range $field := .Type.Fields }}
   101  			case "{{ $field.Name }}":
   102  				{{- if $field.IsOptional }}
   103  				if n.{{ $field | FieldSymbolLower }}.m == schema.Maybe_Absent {
   104  					return datamodel.Absent, nil
   105  				}
   106  				{{- end}}
   107  				{{- if $field.IsNullable }}
   108  				if n.{{ $field | FieldSymbolLower }}.m == schema.Maybe_Null {
   109  					return datamodel.Null, nil
   110  				}
   111  				{{- end}}
   112  				{{- if $field.IsMaybe }}
   113  				return {{if not (MaybeUsesPtr $field.Type) }}&{{end}}n.{{ $field | FieldSymbolLower }}.v, nil
   114  				{{- else}}
   115  				return &n.{{ $field | FieldSymbolLower }}, nil
   116  				{{- end}}
   117  			{{- end}}
   118  			default:
   119  				return nil, schema.ErrNoSuchField{Type: nil /*TODO*/, Field: datamodel.PathSegmentOfString(key)}
   120  			}
   121  		}
   122  	`, w, g.AdjCfg, g)
   123  }
   124  
   125  func (g structGenerator) EmitNodeMethodLookupByNode(w io.Writer) {
   126  	doTemplate(`
   127  		func (n {{ .Type | TypeSymbol }}) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
   128  			ks, err := key.AsString()
   129  			if err != nil {
   130  				return nil, err
   131  			}
   132  			return n.LookupByString(ks)
   133  		}
   134  	`, w, g.AdjCfg, g)
   135  }
   136  
   137  func (g structGenerator) EmitNodeMethodMapIterator(w io.Writer) {
   138  	// Note that the typed iterator will report absent fields.
   139  	//  The representation iterator (if has one) however will skip those.
   140  	doTemplate(`
   141  		func (n {{ .Type | TypeSymbol }}) MapIterator() datamodel.MapIterator {
   142  			return &_{{ .Type | TypeSymbol }}__MapItr{n, 0}
   143  		}
   144  
   145  		type _{{ .Type | TypeSymbol }}__MapItr struct {
   146  			n {{ .Type | TypeSymbol }}
   147  			idx  int
   148  		}
   149  
   150  		func (itr *_{{ .Type | TypeSymbol }}__MapItr) Next() (k datamodel.Node, v datamodel.Node, _ error) {
   151  			{{- if not .Type.Fields }}
   152  			return nil, nil, datamodel.ErrIteratorOverread{}
   153  			{{ else -}}
   154  			if itr.idx >= {{ len .Type.Fields }} {
   155  				return nil, nil, datamodel.ErrIteratorOverread{}
   156  			}
   157  			switch itr.idx {
   158  			{{- $type := .Type -}} {{- /* ranging modifies dot, unhelpfully */ -}}
   159  			{{- range $i, $field := .Type.Fields }}
   160  			case {{ $i }}:
   161  				k = &fieldName__{{ $type | TypeSymbol }}_{{ $field | FieldSymbolUpper }}
   162  				{{- if $field.IsOptional }}
   163  				if itr.n.{{ $field | FieldSymbolLower }}.m == schema.Maybe_Absent {
   164  					v = datamodel.Absent
   165  					break
   166  				}
   167  				{{- end}}
   168  				{{- if $field.IsNullable }}
   169  				if itr.n.{{ $field | FieldSymbolLower }}.m == schema.Maybe_Null {
   170  					v = datamodel.Null
   171  					break
   172  				}
   173  				{{- end}}
   174  				{{- if $field.IsMaybe }}
   175  				v = {{if not (MaybeUsesPtr $field.Type) }}&{{end}}itr.n.{{ $field | FieldSymbolLower}}.v
   176  				{{- else}}
   177  				v = &itr.n.{{ $field | FieldSymbolLower}}
   178  				{{- end}}
   179  			{{- end}}
   180  			default:
   181  				panic("unreachable")
   182  			}
   183  			itr.idx++
   184  			return
   185  			{{- end}}
   186  		}
   187  		func (itr *_{{ .Type | TypeSymbol }}__MapItr) Done() bool {
   188  			return itr.idx >= {{ len .Type.Fields }}
   189  		}
   190  
   191  	`, w, g.AdjCfg, g)
   192  }
   193  
   194  func (g structGenerator) EmitNodeMethodLength(w io.Writer) {
   195  	doTemplate(`
   196  		func ({{ .Type | TypeSymbol }}) Length() int64 {
   197  			return {{ len .Type.Fields }}
   198  		}
   199  	`, w, g.AdjCfg, g)
   200  }
   201  
   202  func (g structGenerator) EmitNodeMethodPrototype(w io.Writer) {
   203  	emitNodeMethodPrototype_typical(w, g.AdjCfg, g)
   204  }
   205  
   206  func (g structGenerator) EmitNodePrototypeType(w io.Writer) {
   207  	emitNodePrototypeType_typical(w, g.AdjCfg, g)
   208  }
   209  
   210  // --- NodeBuilder and NodeAssembler --->
   211  
   212  func (g structGenerator) GetNodeBuilderGenerator() NodeBuilderGenerator {
   213  	return structBuilderGenerator{
   214  		g.AdjCfg,
   215  		mixins.MapAssemblerTraits{
   216  			PkgName:       g.PkgName,
   217  			TypeName:      g.TypeName,
   218  			AppliedPrefix: "_" + g.AdjCfg.TypeSymbol(g.Type) + "__",
   219  		},
   220  		g.PkgName,
   221  		g.Type,
   222  	}
   223  }
   224  
   225  type structBuilderGenerator struct {
   226  	AdjCfg *AdjunctCfg
   227  	mixins.MapAssemblerTraits
   228  	PkgName string
   229  	Type    *schema.TypeStruct
   230  }
   231  
   232  func (structBuilderGenerator) IsRepr() bool { return false } // hint used in some generalized templates.
   233  
   234  func (g structBuilderGenerator) EmitNodeBuilderType(w io.Writer) {
   235  	emitEmitNodeBuilderType_typical(w, g.AdjCfg, g)
   236  }
   237  func (g structBuilderGenerator) EmitNodeBuilderMethods(w io.Writer) {
   238  	emitNodeBuilderMethods_typical(w, g.AdjCfg, g)
   239  }
   240  func (g structBuilderGenerator) EmitNodeAssemblerType(w io.Writer) {
   241  	// - 'w' is the "**w**ip" pointer.
   242  	// - 'm' is the **m**aybe which communicates our completeness to the parent if we're a child assembler.
   243  	// - 'state' is what it says on the tin.  this is used for the map state (the broad transitions between null, start-map, and finish are handled by 'm' for consistency.)
   244  	// - 's' is a bitfield for what's been **s**et.
   245  	// - 'f' is the **f**ocused field that will be assembled next.
   246  	//
   247  	// - 'cm' is **c**hild **m**aybe and is used for the completion message from children that aren't allowed to be nullable (for those that are, their own maybe.m is used).
   248  	//    ('cm' could be elided for structs where all fields are maybes.  trivial but not yet implemented.)
   249  	// - the 'ca_*' fields embed **c**hild **a**ssemblers -- these are embedded so we can yield pointers to them without causing new allocations.
   250  	doTemplate(`
   251  		type _{{ .Type | TypeSymbol }}__Assembler struct {
   252  			w *_{{ .Type | TypeSymbol }}
   253  			m *schema.Maybe
   254  			state maState
   255  			s int
   256  			f int
   257  
   258  			cm schema.Maybe
   259  			{{range $field := .Type.Fields -}}
   260  			ca_{{ $field | FieldSymbolLower }} _{{ $field.Type | TypeSymbol }}__Assembler
   261  			{{end -}}
   262  		}
   263  
   264  		func (na *_{{ .Type | TypeSymbol }}__Assembler) reset() {
   265  			na.state = maState_initial
   266  			na.s = 0
   267  			{{- range $field := .Type.Fields }}
   268  			na.ca_{{ $field | FieldSymbolLower }}.reset()
   269  			{{- end}}
   270  		}
   271  
   272  		var (
   273  			{{- $type := .Type -}} {{- /* ranging modifies dot, unhelpfully */ -}}
   274  			{{- range $i, $field := .Type.Fields }}
   275  			fieldBit__{{ $type | TypeSymbol }}_{{ $field | FieldSymbolUpper }} = 1 << {{ $i }}
   276  			{{- end}}
   277  			fieldBits__{{ $type | TypeSymbol }}_sufficient = 0 {{- range $i, $field := .Type.Fields }}{{if not $field.IsOptional }} + 1 << {{ $i }}{{end}}{{end}}
   278  		)
   279  	`, w, g.AdjCfg, g)
   280  }
   281  func (g structBuilderGenerator) EmitNodeAssemblerMethodBeginMap(w io.Writer) {
   282  	emitNodeAssemblerMethodBeginMap_strictoid(w, g.AdjCfg, g)
   283  }
   284  func (g structBuilderGenerator) EmitNodeAssemblerMethodAssignNull(w io.Writer) {
   285  	emitNodeAssemblerMethodAssignNull_recursive(w, g.AdjCfg, g)
   286  }
   287  func (g structBuilderGenerator) EmitNodeAssemblerMethodAssignNode(w io.Writer) {
   288  	// AssignNode goes through three phases:
   289  	// 1. is it null?  Jump over to AssignNull (which may or may not reject it).
   290  	// 2. is it our own type?  Handle specially -- we might be able to do efficient things.
   291  	// 3. is it the right kind to morph into us?  Do so.
   292  	//
   293  	// 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.
   294  	doTemplate(`
   295  		func (na *_{{ .Type | TypeSymbol }}__Assembler) AssignNode(v datamodel.Node) error {
   296  			if v.IsNull() {
   297  				return na.AssignNull()
   298  			}
   299  			if v2, ok := v.(*_{{ .Type | TypeSymbol }}); ok {
   300  				switch *na.m {
   301  				case schema.Maybe_Value, schema.Maybe_Null:
   302  					panic("invalid state: cannot assign into assembler that's already finished")
   303  				case midvalue:
   304  					panic("invalid state: cannot assign null into an assembler that's already begun working on recursive structures!")
   305  				}
   306  				{{- if .Type | MaybeUsesPtr }}
   307  				if na.w == nil {
   308  					na.w = v2
   309  					*na.m = schema.Maybe_Value
   310  					return nil
   311  				}
   312  				{{- end}}
   313  				*na.w = *v2
   314  				*na.m = schema.Maybe_Value
   315  				return nil
   316  			}
   317  			if v.Kind() != datamodel.Kind_Map {
   318  				return datamodel.ErrWrongKind{TypeName: "{{ .PkgName }}.{{ .Type.Name }}", MethodName: "AssignNode", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: v.Kind()}
   319  			}
   320  			itr := v.MapIterator()
   321  			for !itr.Done() {
   322  				k, v, err := itr.Next()
   323  				if err != nil {
   324  					return err
   325  				}
   326  				if err := na.AssembleKey().AssignNode(k); err != nil {
   327  					return err
   328  				}
   329  				if err := na.AssembleValue().AssignNode(v); err != nil {
   330  					return err
   331  				}
   332  			}
   333  			return na.Finish()
   334  		}
   335  	`, w, g.AdjCfg, g)
   336  }
   337  func (g structBuilderGenerator) EmitNodeAssemblerOtherBits(w io.Writer) {
   338  	g.emitMapAssemblerChildTidyHelper(w)
   339  	g.emitMapAssemblerMethods(w)
   340  	g.emitKeyAssembler(w)
   341  }
   342  func (g structBuilderGenerator) emitMapAssemblerChildTidyHelper(w io.Writer) {
   343  	// This function attempts to clean up the state machine to acknolwedge child assembly finish.
   344  	//  If the child was finished and we just collected it, return true and update state to maState_initial.
   345  	//  Otherwise, if it wasn't done, return false;
   346  	//   and the caller is almost certain to emit an error momentarily.
   347  	// The function will only be called when the current state is maState_midValue.
   348  	//  (In general, the idea is that if the user is doing things correctly,
   349  	//   this function will only be called when the child is in fact finished.)
   350  	// Most of the logic here is about nullables and not optionals,
   351  	//  because if you're an optional that's absent, you never got to value assembly.
   352  	//  There's still one branch for optionals, though, because they have a different residence for 'm' just as nullables do.
   353  	// Child assemblers are expected to control their own state machines;
   354  	//  for values that have maybes, we never change their maybe state again, so the usual logic should hold;
   355  	//  for values that don't have maybes (and thus share 'cm')...
   356  	//   We don't bother to nil their 'm' pointer; the worst that can happen is an over-held assembler for that field
   357  	//    can make a bizarre and broken transition for a subsequent field, which will result in very ugly errors, but isn't unsafe per se.
   358  	//   We do nil their 'w' pointer, though: we don't want a set to that able to leak in later if we're on the way to Finish!
   359  	doTemplate(`
   360  		func (ma *_{{ .Type | TypeSymbol }}__Assembler) valueFinishTidy() bool {
   361  			switch ma.f {
   362  			{{- range $i, $field := .Type.Fields }}
   363  			case {{ $i }}:
   364  				{{- if $field.IsNullable }}
   365  				switch ma.w.{{ $field | FieldSymbolLower }}.m {
   366  				case schema.Maybe_Null:
   367  					ma.state = maState_initial
   368  					return true
   369  				case schema.Maybe_Value:
   370  					{{- if (MaybeUsesPtr $field.Type) }}
   371  					ma.w.{{ $field | FieldSymbolLower }}.v = ma.ca_{{ $field | FieldSymbolLower }}.w
   372  					{{- end}}
   373  					ma.state = maState_initial
   374  					return true
   375  				default:
   376  					return false
   377  				}
   378  				{{- else if $field.IsOptional }}
   379  				switch ma.w.{{ $field | FieldSymbolLower }}.m {
   380  				case schema.Maybe_Value:
   381  					{{- if (MaybeUsesPtr $field.Type) }}
   382  					ma.w.{{ $field | FieldSymbolLower }}.v = ma.ca_{{ $field | FieldSymbolLower }}.w
   383  					{{- end}}
   384  					ma.state = maState_initial
   385  					return true
   386  				default:
   387  					return false
   388  				}
   389  				{{- else}}
   390  				switch ma.cm {
   391  				case schema.Maybe_Value:
   392  					ma.ca_{{ $field | FieldSymbolLower }}.w = nil
   393  					ma.cm = schema.Maybe_Absent
   394  					ma.state = maState_initial
   395  					return true
   396  				default:
   397  					return false
   398  				}
   399  				{{- end}}
   400  			{{- end}}
   401  			default:
   402  				panic("unreachable")
   403  			}
   404  		}
   405  	`, w, g.AdjCfg, g)
   406  }
   407  func (g structBuilderGenerator) emitMapAssemblerMethods(w io.Writer) {
   408  	// FUTURE: some of the setup of the child assemblers could probably be DRY'd up.
   409  	doTemplate(`
   410  		func (ma *_{{ .Type | TypeSymbol }}__Assembler) AssembleEntry(k string) (datamodel.NodeAssembler, error) {
   411  			switch ma.state {
   412  			case maState_initial:
   413  				// carry on
   414  			case maState_midKey:
   415  				panic("invalid state: AssembleEntry cannot be called when in the middle of assembling another key")
   416  			case maState_expectValue:
   417  				panic("invalid state: AssembleEntry cannot be called when expecting start of value assembly")
   418  			case maState_midValue:
   419  				if !ma.valueFinishTidy() {
   420  					panic("invalid state: AssembleEntry cannot be called when in the middle of assembling a value")
   421  				} // if tidy success: carry on
   422  			case maState_finished:
   423  				panic("invalid state: AssembleEntry cannot be called on an assembler that's already finished")
   424  			}
   425  			{{- $type := .Type -}} {{- /* ranging modifies dot, unhelpfully */ -}}
   426  			{{- if .Type.Fields }}
   427  			switch k {
   428  			{{- range $i, $field := .Type.Fields }}
   429  			case "{{ $field.Name }}":
   430  				if ma.s & fieldBit__{{ $type | TypeSymbol }}_{{ $field | FieldSymbolUpper }} != 0 {
   431  					return nil, datamodel.ErrRepeatedMapKey{Key: &fieldName__{{ $type | TypeSymbol }}_{{ $field | FieldSymbolUpper }}}
   432  				}
   433  				ma.s += fieldBit__{{ $type | TypeSymbol }}_{{ $field | FieldSymbolUpper }}
   434  				ma.state = maState_midValue
   435  				ma.f = {{ $i }}
   436  				{{- if $field.IsMaybe }}
   437  				ma.ca_{{ $field | FieldSymbolLower }}.w = {{if not (MaybeUsesPtr $field.Type) }}&{{end}}ma.w.{{ $field | FieldSymbolLower }}.v
   438  				ma.ca_{{ $field | FieldSymbolLower }}.m = &ma.w.{{ $field | FieldSymbolLower }}.m
   439  				{{- if $field.IsNullable }}
   440  				ma.w.{{ $field | FieldSymbolLower }}.m = allowNull
   441  				{{- end}}
   442  				{{- else}}
   443  				ma.ca_{{ $field | FieldSymbolLower }}.w = &ma.w.{{ $field | FieldSymbolLower }}
   444  				ma.ca_{{ $field | FieldSymbolLower }}.m = &ma.cm
   445  				{{- end}}
   446  				return &ma.ca_{{ $field | FieldSymbolLower }}, nil
   447  			{{- end}}
   448  			}
   449  			{{- end}}
   450  			return nil, schema.ErrInvalidKey{TypeName:"{{ .PkgName }}.{{ .Type.Name }}", Key:&_String{k}}
   451  		}
   452  		func (ma *_{{ .Type | TypeSymbol }}__Assembler) AssembleKey() datamodel.NodeAssembler {
   453  			switch ma.state {
   454  			case maState_initial:
   455  				// carry on
   456  			case maState_midKey:
   457  				panic("invalid state: AssembleKey cannot be called when in the middle of assembling another key")
   458  			case maState_expectValue:
   459  				panic("invalid state: AssembleKey cannot be called when expecting start of value assembly")
   460  			case maState_midValue:
   461  				if !ma.valueFinishTidy() {
   462  					panic("invalid state: AssembleKey cannot be called when in the middle of assembling a value")
   463  				} // if tidy success: carry on
   464  			case maState_finished:
   465  				panic("invalid state: AssembleKey cannot be called on an assembler that's already finished")
   466  			}
   467  			ma.state = maState_midKey
   468  			return (*_{{ .Type | TypeSymbol }}__KeyAssembler)(ma)
   469  		}
   470  		func (ma *_{{ .Type | TypeSymbol }}__Assembler) AssembleValue() datamodel.NodeAssembler {
   471  			switch ma.state {
   472  			case maState_initial:
   473  				panic("invalid state: AssembleValue cannot be called when no key is primed")
   474  			case maState_midKey:
   475  				panic("invalid state: AssembleValue cannot be called when in the middle of assembling a key")
   476  			case maState_expectValue:
   477  				// carry on
   478  			case maState_midValue:
   479  				panic("invalid state: AssembleValue cannot be called when in the middle of assembling another value")
   480  			case maState_finished:
   481  				panic("invalid state: AssembleValue cannot be called on an assembler that's already finished")
   482  			}
   483  			ma.state = maState_midValue
   484  			switch ma.f {
   485  			{{- range $i, $field := .Type.Fields }}
   486  			case {{ $i }}:
   487  				{{- if $field.IsMaybe }}
   488  				ma.ca_{{ $field | FieldSymbolLower }}.w = {{if not (MaybeUsesPtr $field.Type) }}&{{end}}ma.w.{{ $field | FieldSymbolLower }}.v
   489  				ma.ca_{{ $field | FieldSymbolLower }}.m = &ma.w.{{ $field | FieldSymbolLower }}.m
   490  				{{- if $field.IsNullable }}
   491  				ma.w.{{ $field | FieldSymbolLower }}.m = allowNull
   492  				{{- end}}
   493  				{{- else}}
   494  				ma.ca_{{ $field | FieldSymbolLower }}.w = &ma.w.{{ $field | FieldSymbolLower }}
   495  				ma.ca_{{ $field | FieldSymbolLower }}.m = &ma.cm
   496  				{{- end}}
   497  				return &ma.ca_{{ $field | FieldSymbolLower }}
   498  			{{- end}}
   499  			default:
   500  				panic("unreachable")
   501  			}
   502  		}
   503  		func (ma *_{{ .Type | TypeSymbol }}__Assembler) Finish() error {
   504  			switch ma.state {
   505  			case maState_initial:
   506  				// carry on
   507  			case maState_midKey:
   508  				panic("invalid state: Finish cannot be called when in the middle of assembling a key")
   509  			case maState_expectValue:
   510  				panic("invalid state: Finish cannot be called when expecting start of value assembly")
   511  			case maState_midValue:
   512  				if !ma.valueFinishTidy() {
   513  					panic("invalid state: Finish cannot be called when in the middle of assembling a value")
   514  				} // if tidy success: carry on
   515  			case maState_finished:
   516  				panic("invalid state: Finish cannot be called on an assembler that's already finished")
   517  			}
   518  			if ma.s & fieldBits__{{ $type | TypeSymbol }}_sufficient != fieldBits__{{ $type | TypeSymbol }}_sufficient {
   519  				err := schema.ErrMissingRequiredField{Missing: make([]string, 0)}
   520  				{{- range $i, $field := .Type.Fields }}
   521  				{{- if not $field.IsMaybe}}
   522  				if ma.s & fieldBit__{{ $type | TypeSymbol }}_{{ $field | FieldSymbolUpper }} == 0 {
   523  					err.Missing = append(err.Missing, "{{ $field.Name }}")
   524  				}
   525  				{{- end}}
   526  				{{- end}}
   527  				return err
   528  			}
   529  			ma.state = maState_finished
   530  			*ma.m = schema.Maybe_Value
   531  			return nil
   532  		}
   533  		func (ma *_{{ .Type | TypeSymbol }}__Assembler) KeyPrototype() datamodel.NodePrototype {
   534  			return _String__Prototype{}
   535  		}
   536  		func (ma *_{{ .Type | TypeSymbol }}__Assembler) ValuePrototype(k string) datamodel.NodePrototype {
   537  			panic("todo structbuilder mapassembler valueprototype")
   538  		}
   539  	`, w, g.AdjCfg, g)
   540  }
   541  func (g structBuilderGenerator) emitKeyAssembler(w io.Writer) {
   542  	doTemplate(`
   543  		type _{{ .Type | TypeSymbol }}__KeyAssembler _{{ .Type | TypeSymbol }}__Assembler
   544  	`, w, g.AdjCfg, g)
   545  	stubs := mixins.StringAssemblerTraits{
   546  		PkgName:       g.PkgName,
   547  		TypeName:      g.TypeName + ".KeyAssembler",
   548  		AppliedPrefix: "_" + g.AdjCfg.TypeSymbol(g.Type) + "__Key",
   549  	}
   550  	// This key assembler can disregard any idea of complex keys because it's a struct!
   551  	//  Struct field names must be strings (and quite simple ones at that).
   552  	stubs.EmitNodeAssemblerMethodBeginMap(w)
   553  	stubs.EmitNodeAssemblerMethodBeginList(w)
   554  	stubs.EmitNodeAssemblerMethodAssignNull(w)
   555  	stubs.EmitNodeAssemblerMethodAssignBool(w)
   556  	stubs.EmitNodeAssemblerMethodAssignInt(w)
   557  	stubs.EmitNodeAssemblerMethodAssignFloat(w)
   558  	doTemplate(`
   559  		func (ka *_{{ .Type | TypeSymbol }}__KeyAssembler) AssignString(k string) error {
   560  			if ka.state != maState_midKey {
   561  				panic("misuse: KeyAssembler held beyond its valid lifetime")
   562  			}
   563  			switch k {
   564  			{{- $type := .Type -}} {{- /* ranging modifies dot, unhelpfully */ -}}
   565  			{{- range $i, $field := .Type.Fields }}
   566  			case "{{ $field.Name }}":
   567  				if ka.s & fieldBit__{{ $type | TypeSymbol }}_{{ $field | FieldSymbolUpper }} != 0 {
   568  					return datamodel.ErrRepeatedMapKey{Key: &fieldName__{{ $type | TypeSymbol }}_{{ $field | FieldSymbolUpper }}}
   569  				}
   570  				ka.s += fieldBit__{{ $type | TypeSymbol }}_{{ $field | FieldSymbolUpper }}
   571  				ka.state = maState_expectValue
   572  				ka.f = {{ $i }}
   573  				return nil
   574  			{{- end}}
   575  			default:
   576  				return schema.ErrInvalidKey{TypeName:"{{ .PkgName }}.{{ .Type.Name }}", Key:&_String{k}}
   577  			}
   578  		}
   579  	`, w, g.AdjCfg, g)
   580  	stubs.EmitNodeAssemblerMethodAssignBytes(w)
   581  	stubs.EmitNodeAssemblerMethodAssignLink(w)
   582  	doTemplate(`
   583  		func (ka *_{{ .Type | TypeSymbol }}__KeyAssembler) AssignNode(v datamodel.Node) error {
   584  			if v2, err := v.AsString(); err != nil {
   585  				return err
   586  			} else {
   587  				return ka.AssignString(v2)
   588  			}
   589  		}
   590  		func (_{{ .Type | TypeSymbol }}__KeyAssembler) Prototype() datamodel.NodePrototype {
   591  			return _String__Prototype{}
   592  		}
   593  	`, w, g.AdjCfg, g)
   594  }