Skip to content

Commit

Permalink
Represent assignment path as a strongly typed path
Browse files Browse the repository at this point in the history
  • Loading branch information
K-Phoen committed Oct 6, 2023
1 parent e380a66 commit 21e89e9
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 78 deletions.
112 changes: 98 additions & 14 deletions internal/ast/builder.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
package ast

import (
"fmt"
"strings"

"github.com/grafana/cog/internal/tools"
)

type Builder struct {
// Original data used to derive the builder, stored for read-only access
// for the jennies and veneers.
Expand All @@ -15,6 +22,55 @@ type Builder struct {
Initializations []Assignment
}

func (builder Builder) MakePath(builders Builders, pathAsString string) (Path, error) {
if pathAsString == "" {
return nil, fmt.Errorf("can not make path from empty input")
}

resolveRef := func(ref RefType) (Builder, error) {
referredObjBuilder, found := builders.LocateByObject(ref.ReferredPkg, ref.ReferredType)
if !found {
return Builder{}, fmt.Errorf("could not make path '%s': reference '%s' could not be resolved", pathAsString, ref.String())
}

return referredObjBuilder, nil
}

currentType := builder.For.Type

var path Path

pathParts := strings.Split(pathAsString, ".")
for _, part := range pathParts {
if currentType.Kind == KindRef {
referredObjBuilder, err := resolveRef(currentType.AsRef())
if err != nil {
return nil, err
}

currentType = referredObjBuilder.For.Type
}

if currentType.Kind != KindStruct {
return nil, fmt.Errorf("could not make path '%s': type at path '%s' is not a struct or a ref", pathAsString, path.String())
}

field, found := currentType.AsStruct().FieldByName(part)
if !found {
return nil, fmt.Errorf("could not make path '%s': field '%s' not found under path '%s'", pathAsString, part, path.String())
}

path = append(path, PathItem{
Identifier: part,
Type: field.Type,
})

currentType = field.Type
}

return path, nil
}

type Builders []Builder

func (builders Builders) LocateByObject(pkg string, name string) (Builder, bool) {
Expand Down Expand Up @@ -45,19 +101,51 @@ type Argument struct {
Type Type
}

type PathItem struct {
Identifier string
Type Type // any
// useful mostly for composability purposes, when a field Type is "any"
// and we're trying to "compose in" something of a known type.
TypeHint *Type
}

type Path []PathItem

func PathFromStructField(field StructField) Path {
return Path{
{
Identifier: field.Name,
Type: field.Type,
},
}
}

func (path Path) Append(suffix Path) Path {
newPath := path[:]
newPath = append(newPath, suffix...)

return newPath
}

func (path Path) Last() PathItem {
return path[len(path)-1]
}

func (path Path) String() string {
return strings.Join(tools.Map(path, func(t PathItem) string {
return t.Identifier
}), ".")
}

type Assignment struct {
// Where
Path string
Path Path

// What
ValueType Type // type of the value being assigned
ArgumentName string // if empty, then use `Value`
Value any

Constraints []TypeConstraint

// Some more context on the what
IntoNullableField bool
}

type BuilderGenerator struct {
Expand Down Expand Up @@ -132,10 +220,8 @@ func (generator *BuilderGenerator) fieldHasStaticValue(field StructField) bool {

func (generator *BuilderGenerator) structFieldToStaticInitialization(field StructField) Assignment {
return Assignment{
Path: field.Name,
Value: field.Type.AsScalar().Value,
ValueType: field.Type,
IntoNullableField: field.Type.Nullable,
Path: PathFromStructField(field),
Value: field.Type.AsScalar().Value,
}
}

Expand All @@ -156,11 +242,9 @@ func (generator *BuilderGenerator) structFieldToOption(field StructField) Option
},
Assignments: []Assignment{
{
Path: field.Name,
ArgumentName: field.Name,
ValueType: field.Type,
Constraints: constraints,
IntoNullableField: field.Type.Nullable,
Path: PathFromStructField(field),
ArgumentName: field.Name,
Constraints: constraints,
},
},
}
Expand Down
8 changes: 8 additions & 0 deletions internal/ast/types.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package ast

import (
"fmt"
)

type Kind string

const (
Expand Down Expand Up @@ -571,6 +575,10 @@ type RefType struct {
ReferredType string
}

func (t RefType) String() string {
return fmt.Sprintf("%s.%s", t.ReferredPkg, t.ReferredType)
}

func (t RefType) DeepCopy() RefType {
return RefType{
ReferredPkg: t.ReferredPkg,
Expand Down
37 changes: 23 additions & 14 deletions internal/jennies/golang/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,22 +158,31 @@ func (jenny *Builder) generateConstructor(builders ast.Builders, builder ast.Bui
return buffer.String()
}

func (jenny *Builder) formatFieldPath(fieldPath string) string {
parts := strings.Split(fieldPath, ".")
formatted := make([]string, 0, len(parts))
func (jenny *Builder) formatFieldPath(fieldPath ast.Path) string {
parts := tools.Map(fieldPath, func(t ast.PathItem) string {
output := tools.UpperCamelCase(t.Identifier)

for _, part := range parts {
formatted = append(formatted, tools.UpperCamelCase(part))
}
if !t.Type.IsAny() || t.TypeHint == nil {
return output
}

formattedTypeHint := formatType(*t.TypeHint, func(pkg string) string {
jenny.imports.Add(pkg, fmt.Sprintf("github.com/grafana/cog/generated/types/%s", pkg))

return pkg
})

return output + fmt.Sprintf(".(*%s)", formattedTypeHint)
})

return strings.Join(formatted, ".")
return strings.Join(parts, ".")
}

func (jenny *Builder) generateInitAssignment(builders ast.Builders, builder ast.Builder, assignment ast.Assignment) string {
fieldPath := jenny.formatFieldPath(assignment.Path)
valueType := assignment.ValueType
valueType := assignment.Path.Last().Type

if _, valueHasBuilder := jenny.builderForType(builders, builder, assignment.ValueType); valueHasBuilder {
if _, valueHasBuilder := jenny.builderForType(builders, builder, valueType); valueHasBuilder {
return "constructor init assignment with type that has a builder is not supported yet"
}

Expand All @@ -185,7 +194,7 @@ func (jenny *Builder) generateInitAssignment(builders ast.Builders, builder ast.

asPointer := ""
// FIXME: this condition is probably wrong
if valueType.Kind != ast.KindArray && valueType.Kind != ast.KindStruct && assignment.IntoNullableField {
if valueType.Kind != ast.KindArray && valueType.Kind != ast.KindStruct && valueType.Nullable {
asPointer = "&"
}

Expand Down Expand Up @@ -272,12 +281,12 @@ func (jenny *Builder) generateArgument(builders ast.Builders, builder ast.Builde

func (jenny *Builder) generateAssignment(builders ast.Builders, builder ast.Builder, assignment ast.Assignment) string {
fieldPath := jenny.formatFieldPath(assignment.Path)
valueType := assignment.ValueType
valueType := assignment.Path.Last().Type

if referredBuilder, found := jenny.builderForType(builders, builder, assignment.ValueType); found {
if referredBuilder, found := jenny.builderForType(builders, builder, valueType); found {
referredBuilderAlias := jenny.importBuilder(referredBuilder)
intoPointer := "*"
if assignment.IntoNullableField {
if valueType.Nullable {
intoPointer = ""
}

Expand All @@ -298,7 +307,7 @@ func (jenny *Builder) generateAssignment(builders ast.Builders, builder ast.Buil

asPointer := ""
// FIXME: this condition is probably wrong
if valueType.Kind != ast.KindArray && valueType.Kind != ast.KindMap && assignment.IntoNullableField {
if valueType.Kind != ast.KindArray && valueType.Kind != ast.KindMap && valueType.Nullable {
asPointer = "&"
}

Expand Down
6 changes: 4 additions & 2 deletions internal/jennies/typescript/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,9 @@ func (jenny *Builder) builderForType(builders ast.Builders, builder ast.Builder,

func (jenny *Builder) generateInitAssignment(builders ast.Builders, builder ast.Builder, assignment ast.Assignment) string {
fieldPath := assignment.Path
valueType := assignment.Path.Last().Type

if _, valueHasBuilder := jenny.builderForType(builders, builder, assignment.ValueType); valueHasBuilder {
if _, valueHasBuilder := jenny.builderForType(builders, builder, valueType); valueHasBuilder {
return "constructor init assignment with type that has a builder is not supported yet"
}

Expand Down Expand Up @@ -300,8 +301,9 @@ func (jenny *Builder) generateArgument(builders ast.Builders, builder ast.Builde

func (jenny *Builder) generateAssignment(builders ast.Builders, builder ast.Builder, assignment ast.Assignment) string {
fieldPath := assignment.Path
valueType := assignment.Path.Last().Type

if _, found := jenny.builderForType(builders, builder, assignment.ValueType); found {
if _, found := jenny.builderForType(builders, builder, valueType); found {
return fmt.Sprintf("\t\tthis.internal.%[1]s = %[2]s.build();", fieldPath, assignment.ArgumentName)
}

Expand Down
10 changes: 10 additions & 0 deletions internal/tools/arrays.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,13 @@ func ItemInList[T comparable](needle T, haystack []T) bool {

return false
}

func Map[T any, O any](input []T, mapper func(T) O) []O {
output := make([]O, len(input))

for i := range input {
output[i] = mapper(input[i])
}

return output
}
Loading

0 comments on commit 21e89e9

Please sign in to comment.