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

     1  package gengo
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  
     8  	"github.com/ipld/go-ipld-prime/datamodel"
     9  	"github.com/ipld/go-ipld-prime/node/basicnode"
    10  	"github.com/ipld/go-ipld-prime/schema"
    11  )
    12  
    13  // This entire file is placeholder-quality implementations.
    14  //
    15  // The AdjunctCfg struct should be replaced with an IPLD Schema-specified thing!
    16  // The values in the unionMemlayout field should be an enum;
    17  // etcetera!
    18  
    19  type FieldTuple struct {
    20  	TypeName  schema.TypeName
    21  	FieldName string
    22  }
    23  
    24  type AdjunctCfg struct {
    25  	typeSymbolOverrides       map[schema.TypeName]string
    26  	FieldSymbolLowerOverrides map[FieldTuple]string
    27  	fieldSymbolUpperOverrides map[FieldTuple]string
    28  	maybeUsesPtr              map[schema.TypeName]bool   // absent uses a heuristic
    29  	CfgUnionMemlayout         map[schema.TypeName]string // "embedAll"|"interface"; maybe more options later, unclear for now.
    30  
    31  	// ... some of these fields have sprouted messy name prefixes so they don't collide with their matching method names.
    32  	//  this structure has reached the critical threshhold where it due to be cleaned up and taken seriously.
    33  
    34  	// note: PkgName doesn't appear in here, because it's...
    35  	//  not adjunct data.  it's a generation invocation parameter.
    36  	//   ... this might not hold up in the future though.
    37  	//    There are unanswered questions about how (also, tbf, *if*) we'll handle generation of multiple packages which use each other's types.
    38  }
    39  
    40  // TypeSymbol returns the symbol for a type;
    41  // by default, it's the same string as its name in the schema,
    42  // but it can be overriden.
    43  //
    44  // This is the base, unembellished symbol.
    45  // It's frequently augmented:
    46  // prefixing an underscore to make it unexported;
    47  // suffixing "__Something" to make the name of a supporting type;
    48  // etc.
    49  // (Most such augmentations are not configurable.)
    50  func (cfg *AdjunctCfg) TypeSymbol(t schema.Type) string {
    51  	if x, ok := cfg.typeSymbolOverrides[t.Name()]; ok {
    52  		return x
    53  	}
    54  	return string(t.Name()) // presumed already upper
    55  }
    56  
    57  func (cfg *AdjunctCfg) FieldSymbolLower(f schema.StructField) string {
    58  	if x, ok := cfg.FieldSymbolLowerOverrides[FieldTuple{f.Parent().Name(), f.Name()}]; ok {
    59  		return x
    60  	}
    61  	return f.Name() // presumed already lower
    62  }
    63  
    64  func (cfg *AdjunctCfg) FieldSymbolUpper(f schema.StructField) string {
    65  	if x, ok := cfg.fieldSymbolUpperOverrides[FieldTuple{f.Type().Name(), f.Name()}]; ok {
    66  		return x
    67  	}
    68  	return strings.Title(f.Name()) //lint:ignore SA1019 cases.Title doesn't work for this
    69  }
    70  
    71  // Comments returns a bool for whether comments should be included in gen output or not.
    72  func (cfg *AdjunctCfg) Comments() bool {
    73  	return true // FUTURE: okay, maybe this should be configurable :)
    74  }
    75  
    76  func (cfg *AdjunctCfg) MaybeUsesPtr(t schema.Type) bool {
    77  	if x, ok := cfg.maybeUsesPtr[t.Name()]; ok {
    78  		return x
    79  	}
    80  
    81  	// As a simple heuristic,
    82  	// check how large the Go representation of this type will be.
    83  	// If it weighs little, we estimate that a pointer is not worthwhile,
    84  	// as storing the data directly will barely take more memory.
    85  	// Plus, the resulting code will be shorter and have fewer branches.
    86  	return sizeOfSchemaType(t) > sizeSmallEnoughForInlining
    87  }
    88  
    89  var (
    90  	// The cutoff for "weighs little" is any size up to this number.
    91  	// It's hasn't been measured with any benchmarks or stats just yet.
    92  	// It's possible that, with those, it might increase in the future.
    93  	// Intuitively, any type 4x the size of a pointer is fine to inline.
    94  	// Adding a pointer will already add 1x overhead, anyway.
    95  	sizeSmallEnoughForInlining = 4 * reflect.TypeOf(new(int)).Size()
    96  
    97  	sizeOfTypeKind [128]uintptr
    98  )
    99  
   100  func init() {
   101  	// Uncomment for debugging.
   102  	// fmt.Fprintf(os.Stderr, "sizeOf(small): %d (4x pointer size)\n", sizeSmallEnoughForInlining)
   103  
   104  	// Get the basic node sizes via basicnode.
   105  	for _, tk := range []struct {
   106  		typeKind  schema.TypeKind
   107  		prototype datamodel.NodePrototype
   108  	}{
   109  		{schema.TypeKind_Bool, basicnode.Prototype.Bool},
   110  		{schema.TypeKind_Int, basicnode.Prototype.Int},
   111  		{schema.TypeKind_Float, basicnode.Prototype.Float},
   112  		{schema.TypeKind_String, basicnode.Prototype.String},
   113  		{schema.TypeKind_Bytes, basicnode.Prototype.Bytes},
   114  		{schema.TypeKind_List, basicnode.Prototype.List},
   115  		{schema.TypeKind_Map, basicnode.Prototype.Map},
   116  		{schema.TypeKind_Link, basicnode.Prototype.Link},
   117  	} {
   118  		nb := tk.prototype.NewBuilder()
   119  		switch tk.typeKind {
   120  		case schema.TypeKind_List:
   121  			am, err := nb.BeginList(0)
   122  			if err != nil {
   123  				panic(err)
   124  			}
   125  			if err := am.Finish(); err != nil {
   126  				panic(err)
   127  			}
   128  		case schema.TypeKind_Map:
   129  			am, err := nb.BeginMap(0)
   130  			if err != nil {
   131  				panic(err)
   132  			}
   133  			if err := am.Finish(); err != nil {
   134  				panic(err)
   135  			}
   136  		}
   137  		// Note that the Node interface has a pointer underneath,
   138  		// so we use Elem to reach the underlying type.
   139  		size := reflect.TypeOf(nb.Build()).Elem().Size()
   140  		sizeOfTypeKind[tk.typeKind] = size
   141  
   142  		// Uncomment for debugging.
   143  		// fmt.Fprintf(os.Stderr, "sizeOf(%s): %d\n", tk.typeKind, size)
   144  	}
   145  }
   146  
   147  // sizeOfSchemaType returns the size of a schema type,
   148  // relative to the size of a pointer in native Go.
   149  //
   150  // For example, TypeInt and TypeMap returns 1, but TypeList returns 3, as a
   151  // slice in Go has a pointer and two integers for length and capacity.
   152  // Any basic type smaller than a pointer, such as TypeBool, returns 1.
   153  func sizeOfSchemaType(t schema.Type) uintptr {
   154  	kind := t.TypeKind()
   155  
   156  	// If this TypeKind is represented by the basicnode package,
   157  	// we statically know its size and we can return here.
   158  	if size := sizeOfTypeKind[kind]; size > 0 {
   159  		return size
   160  	}
   161  
   162  	// TODO: handle typekinds like structs, unions, etc.
   163  	// For now, return a large size to fall back to using a pointer.
   164  	return 100 * sizeSmallEnoughForInlining
   165  }
   166  
   167  // UnionMemlayout returns a plain string at present;
   168  // there's a case-switch in the templates that processes it.
   169  // We validate that it's a known string when this method is called.
   170  // This should probably be improved in type-safety,
   171  // and validated more aggressively up front when adjcfg is loaded.
   172  func (cfg *AdjunctCfg) UnionMemlayout(t schema.Type) string {
   173  	if t.TypeKind() != schema.TypeKind_Union {
   174  		panic(fmt.Errorf("%s is not a union", t.Name()))
   175  	}
   176  	v, ok := cfg.CfgUnionMemlayout[t.Name()]
   177  	if !ok {
   178  		return "embedAll"
   179  	}
   180  	switch v {
   181  	case "embedAll", "interface":
   182  		return v
   183  	default:
   184  		panic(fmt.Errorf("invalid config: unionMemlayout values must be either \"embedAll\" or \"interface\", not %q", v))
   185  	}
   186  }