Skip to content
This repository was archived by the owner on Apr 30, 2025. It is now read-only.

Commit 2f7047e

Browse files
committed
Merge branch 'main' of github.com:grafana/cuetsy into fix-type-default-overrides
2 parents 06618fc + 325fc6c commit 2f7047e

File tree

3 files changed

+158
-50
lines changed

3 files changed

+158
-50
lines changed

generator.go

Lines changed: 109 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -214,15 +214,15 @@ func (g *generator) genType(name string, v cue.Value) []ts.Decl {
214214
switch op {
215215
case cue.OrOp:
216216
for _, dv := range dvals {
217-
tok, err := tsprintField(dv, true)
217+
tok, err := g.tsprintField(dv, true)
218218
if err != nil {
219219
g.addErr(err)
220220
return nil
221221
}
222222
tokens = append(tokens, tok)
223223
}
224224
case cue.NoOp, cue.RegexMatchOp:
225-
tok, err := tsprintField(v, true)
225+
tok, err := g.tsprintField(v, true)
226226
if err != nil {
227227
g.addErr(err)
228228
return nil
@@ -246,7 +246,7 @@ func (g *generator) genType(name string, v cue.Value) []ts.Decl {
246246
return ret[:1]
247247
}
248248

249-
val, err := tsprintField(d, false)
249+
val, err := g.tsprintField(d, false)
250250
g.addErr(err)
251251

252252
def := tsast.VarDecl{
@@ -657,7 +657,7 @@ func (g *generator) genInterfaceField(v cue.Value) (*typeRef, error) {
657657

658658
tref := &typeRef{}
659659
var err error
660-
tref.T, err = tsprintField(v, true)
660+
tref.T, err = g.tsprintField(v, true)
661661
if err != nil {
662662
if !containsCuetsyReference(v) {
663663
g.addErr(valError(v, "could not generate field: %w", err))
@@ -667,7 +667,7 @@ func (g *generator) genInterfaceField(v cue.Value) (*typeRef, error) {
667667
return nil, nil
668668
}
669669

670-
exists, defExpr, err := tsPrintDefault(v)
670+
exists, defExpr, err := g.tsPrintDefault(v)
671671
if exists {
672672
tref.D = defExpr
673673
}
@@ -729,30 +729,15 @@ func hasTypeReference(v cue.Value) bool {
729729
func (g *generator) genEnumReference(v cue.Value) (*typeRef, error) {
730730
var lit *cue.Value
731731

732-
findIdent := func(ev, tv cue.Value) (*tsast.Ident, error) {
733-
if ev.Subsume(tv) != nil {
734-
err := valError(v, "may only apply values to an enum that are members of that enum; %#v is not a member of %#v", tv, ev)
735-
g.addErr(err)
736-
return nil, err
737-
}
738-
pairs, err := enumPairs(ev)
739-
if err != nil {
740-
return nil, err
741-
}
742-
for _, pair := range pairs {
743-
if veq(pair.val, tv) {
744-
return &tsast.Ident{Name: pair.name}, nil
745-
}
746-
}
747-
748-
panic(fmt.Sprintf("unreachable - %#v not equal to any member of %#v, but should have been caught by subsume check", tv, ev))
749-
}
750-
751732
conjuncts := appendSplit(nil, cue.AndOp, v)
733+
var enumUnions map[cue.Value]cue.Value
752734
switch len(conjuncts) {
753735
case 0:
754736
panic("unreachable")
755737
case 1:
738+
// This case is when we have a union of enums which we need to iterate them to get their values or has a default value.
739+
// It retrieves a list of literals with their references.
740+
enumUnions = g.findEnumUnions(v)
756741
case 2:
757742
var err error
758743
conjuncts[1] = getDefaultEnumValue(conjuncts[1])
@@ -789,15 +774,15 @@ func (g *generator) genEnumReference(v cue.Value) (*typeRef, error) {
789774
// Search the expr tree for the actual enum. This approach is uncomfortable
790775
// without having the assurance that there aren't more than one possible match/a
791776
// guarantee from the CUE API of a stable, deterministic search order, etc.
792-
ev, referrer, has := findRefWithKind(v, TypeEnum)
777+
enumValues, referrer, has := findRefWithKind(v, TypeEnum)
793778
if !has {
794779
ve := valError(v, "does not reference a field with a cuetsy enum attribute")
795780
g.addErr(ve)
796781
return nil, fmt.Errorf("no enum attr in %s", v)
797782
}
798783

799784
var err error
800-
decls := g.genEnum("foo", ev)
785+
decls := g.genEnum("foo", enumValues)
801786
ref := &typeRef{}
802787

803788
// Construct the type component of the reference
@@ -807,7 +792,7 @@ func (g *generator) genEnumReference(v cue.Value) (*typeRef, error) {
807792
g.addErr(ve)
808793
return nil, ve
809794
case 1, 2:
810-
ref.T, err = referenceValueAs(referrer)
795+
ref.T, err = referenceValueAs(referrer, TypeEnum)
811796
if err != nil {
812797
panic(err)
813798
}
@@ -818,19 +803,34 @@ func (g *generator) genEnumReference(v cue.Value) (*typeRef, error) {
818803
switch len(conjuncts) {
819804
case 1:
820805
if defv, hasdef := v.Default(); hasdef {
821-
if defaultIdent, err := findIdent(ev, defv); err == nil {
822-
ref.D = tsast.SelectorExpr{Expr: ref.T, Sel: *defaultIdent}
823-
} else {
824-
return nil, err
825-
}
806+
err = g.findIdent(v, enumValues, defv, func(expr tsast.Ident) {
807+
ref.D = tsast.SelectorExpr{Expr: ref.T, Sel: expr}
808+
})
809+
}
810+
if len(enumUnions) == 0 {
811+
break
826812
}
813+
var elements []tsast.Expr
814+
for lit, enumValues := range enumUnions {
815+
err = g.findIdent(v, enumValues, lit, func(ident tsast.Ident) {
816+
elements = append(elements, tsast.SelectorExpr{
817+
Expr: ref.T,
818+
Sel: ident,
819+
})
820+
})
821+
}
822+
823+
// To avoid to change the order of the elements everytime that we generate the code.
824+
sort.Slice(elements, func(i, j int) bool {
825+
return elements[i].String() < elements[j].String()
826+
})
827+
828+
ref.T = ts.Union(elements...)
827829
case 2, 3:
828830
var rr tsast.Expr
829-
if defaultIdent, err := findIdent(ev, *lit); err == nil {
830-
rr = tsast.SelectorExpr{Expr: ref.T, Sel: *defaultIdent}
831-
} else {
832-
return nil, err
833-
}
831+
err = g.findIdent(v, enumValues, *lit, func(ident tsast.Ident) {
832+
rr = tsast.SelectorExpr{Expr: ref.T, Sel: ident}
833+
})
834834

835835
op, args := v.Expr()
836836
hasInnerDefault := false
@@ -845,7 +845,64 @@ func (g *generator) genEnumReference(v cue.Value) (*typeRef, error) {
845845
}
846846
}
847847

848-
return ref, nil
848+
return ref, err
849+
}
850+
851+
// findEnumUnions find the unions between enums like (#Enum & "a") | (#Enum & "b")
852+
func (g generator) findEnumUnions(v cue.Value) map[cue.Value]cue.Value {
853+
op, values := v.Expr()
854+
if op != cue.OrOp {
855+
return nil
856+
}
857+
858+
enumsWithUnions := make(map[cue.Value]cue.Value, len(values))
859+
for _, val := range values {
860+
conjuncts := appendSplit(nil, cue.AndOp, val)
861+
if len(conjuncts) != 2 {
862+
return nil
863+
}
864+
cr, lit := conjuncts[0], conjuncts[1]
865+
if cr.Subsume(lit) != nil {
866+
return nil
867+
}
868+
869+
switch val.Kind() {
870+
case cue.StringKind, cue.IntKind:
871+
enumValues, _, has := findRefWithKind(v, TypeEnum)
872+
if !has {
873+
return nil
874+
}
875+
enumsWithUnions[lit] = enumValues
876+
default:
877+
_, vals := val.Expr()
878+
if len(vals) > 1 {
879+
panic(fmt.Sprintf("%s.%s isn't a valid enum value", val.Path().String(), vals[1]))
880+
}
881+
panic(fmt.Sprintf("Invalid value in path %s", val.Path().String()))
882+
}
883+
}
884+
885+
return enumsWithUnions
886+
}
887+
888+
func (g generator) findIdent(v, ev, tv cue.Value, fn func(tsast.Ident)) error {
889+
if ev.Subsume(tv) != nil {
890+
err := valError(v, "may only apply values to an enum that are members of that enum; %#v is not a member of %#v", tv, ev)
891+
g.addErr(err)
892+
return err
893+
}
894+
pairs, err := enumPairs(ev)
895+
if err != nil {
896+
return err
897+
}
898+
for _, pair := range pairs {
899+
if veq(pair.val, tv) {
900+
fn(tsast.Ident{Name: pair.name})
901+
return nil
902+
}
903+
}
904+
905+
panic(fmt.Sprintf("unreachable - %#v not equal to any member of %#v, but should have been caught by subsume check", tv, ev))
849906
}
850907

851908
func getEnumLiteral(conjuncts []cue.Value) (*cue.Value, error) {
@@ -904,7 +961,7 @@ type typeRef struct {
904961
D ts.Expr
905962
}
906963

907-
func tsPrintDefault(v cue.Value) (bool, ts.Expr, error) {
964+
func (g generator) tsPrintDefault(v cue.Value) (bool, ts.Expr, error) {
908965
d, ok := v.Default()
909966
// [...number] results in [], which is a fake default, we need to correct it here.
910967
// if ok && d.Kind() == cue.ListKind {
@@ -933,7 +990,7 @@ func tsPrintDefault(v cue.Value) (bool, ts.Expr, error) {
933990
// }
934991

935992
if ok {
936-
expr, err := tsprintField(d, false)
993+
expr, err := g.tsprintField(d, false)
937994
if err != nil {
938995
return false, nil, err
939996
}
@@ -959,12 +1016,17 @@ func tsPrintDefault(v cue.Value) (bool, ts.Expr, error) {
9591016

9601017
// Render a string containing a Typescript semantic equivalent to the provided
9611018
// Value for placement in a single field, if possible.
962-
func tsprintField(v cue.Value, isType bool) (ts.Expr, error) {
1019+
func (g generator) tsprintField(v cue.Value, isType bool) (ts.Expr, error) {
9631020
// Let the forceText attribute supersede everything.
9641021
if ft := getForceText(v); ft != "" {
9651022
return ts.Raw(ft), nil
9661023
}
9671024

1025+
if hasEnumReference(v) {
1026+
ref, err := g.genEnumReference(v)
1027+
return ref.T, err
1028+
}
1029+
9681030
// References are orthogonal to the Kind system. Handle them first.
9691031
if hasTypeReference(v) || containsCuetsyReference(v, TypeInterface) || hasEnumReference(v) {
9701032
ref, err := referenceValueAs(v)
@@ -996,7 +1058,7 @@ func tsprintField(v cue.Value, isType bool) (ts.Expr, error) {
9961058
// It skips structs like {...} (cue.TopKind) to avoid undesired results.
9971059
val := v.LookupPath(cue.MakePath(cue.AnyString))
9981060
if val.Exists() && val.IncompleteKind() != cue.TopKind {
999-
expr, err := tsprintField(val, isType)
1061+
expr, err := g.tsprintField(val, isType)
10001062
if err != nil {
10011063
return nil, valError(v, err.Error())
10021064
}
@@ -1017,7 +1079,7 @@ func tsprintField(v cue.Value, isType bool) (ts.Expr, error) {
10171079
size, _ := v.Len().Int64()
10181080
kvs := make([]tsast.KeyValueExpr, 0, size)
10191081
for iter.Next() {
1020-
expr, err := tsprintField(iter.Value(), isType)
1082+
expr, err := g.tsprintField(iter.Value(), isType)
10211083
if err != nil {
10221084
return nil, valError(v, err.Error())
10231085
}
@@ -1047,7 +1109,7 @@ func tsprintField(v cue.Value, isType bool) (ts.Expr, error) {
10471109
iter, _ := v.List()
10481110
var elems []ts.Expr
10491111
for iter.Next() {
1050-
e, err := tsprintField(iter.Value(), isType)
1112+
e, err := g.tsprintField(iter.Value(), isType)
10511113
if err != nil {
10521114
return nil, err
10531115
}
@@ -1059,12 +1121,11 @@ func tsprintField(v cue.Value, isType bool) (ts.Expr, error) {
10591121
case cue.BytesKind:
10601122
return nil, valError(v, "bytes have no equivalent in Typescript; use double-quotes (string) instead")
10611123
}
1062-
10631124
// Handler for disjunctions
10641125
disj := func(dvals []cue.Value) (ts.Expr, error) {
10651126
parts := make([]ts.Expr, 0, len(dvals))
10661127
for _, dv := range dvals {
1067-
p, err := tsprintField(dv, isType)
1128+
p, err := g.tsprintField(dv, isType)
10681129
if err != nil {
10691130
return nil, err
10701131
}
@@ -1113,7 +1174,7 @@ func tsprintField(v cue.Value, isType bool) (ts.Expr, error) {
11131174

11141175
e := v.LookupPath(cue.MakePath(cue.AnyIndex))
11151176
if e.Exists() {
1116-
expr, err := tsprintField(e, isType)
1177+
expr, err := g.tsprintField(e, isType)
11171178
if err != nil {
11181179
return nil, err
11191180
}
@@ -1152,7 +1213,7 @@ func tsprintField(v cue.Value, isType bool) (ts.Expr, error) {
11521213
switch op {
11531214
case cue.OrOp:
11541215
if len(dvals) == 2 && dvals[0].Kind() == cue.NullKind {
1155-
return tsprintField(dvals[1], isType)
1216+
return g.tsprintField(dvals[1], isType)
11561217
}
11571218
return disj(dvals)
11581219
case cue.NoOp, cue.AndOp:

testdata/imports/compose_enums.txtar

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ export interface Compose {
7979
localstr: LocalEnum;
8080
localstrd: LocalEnumD;
8181
localstrover: LocalEnumD;
82-
union: dep.DepEnumNumericD;
83-
unionStrings: LocalEnum;
82+
union: (dep.DepEnumNumericD.Three | dep.DepEnumNumericD.Two);
83+
unionStrings: (LocalEnum.Bar | LocalEnum.Baz | LocalEnum.Foo);
8484
}
8585

8686
export const defaultCompose: Partial<Compose> = {

testdata/union_enum_types.txtar

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
-- cue --
2+
3+
#StringEnum: "a" | "b" | "c" @cuetsy(kind="enum")
4+
#StringEnumWithMemberTypes: "a" | "b" | "c" @cuetsy(kind="enum",memberNames="First|Second|Third")
5+
#IntEnum: 1 | 2 | 3 @cuetsy(kind="enum",memberNames="First|Second|Third")
6+
7+
#Expressions: {
8+
sEnum: (#StringEnum & "a") | (#StringEnum & "b")
9+
sEnumMem: (#StringEnumWithMemberTypes & "a") | (#StringEnumWithMemberTypes & "b")
10+
iEnum: (#IntEnum & 1) | (#IntEnum & 2) | (#IntEnum & 3)
11+
normal: #StringEnum & "a"
12+
nested: {
13+
nestedEnum: #StringEnum & "a"
14+
nestedUnionEnum: (#StringEnum & "a") | (#StringEnum & "b")
15+
}
16+
} @cuetsy(kind="interface")
17+
18+
-- ts --
19+
20+
export enum StringEnum {
21+
A = 'a',
22+
B = 'b',
23+
C = 'c',
24+
}
25+
26+
export enum StringEnumWithMemberTypes {
27+
First = 'a',
28+
Second = 'b',
29+
Third = 'c',
30+
}
31+
32+
export enum IntEnum {
33+
First = 1,
34+
Second = 2,
35+
Third = 3,
36+
}
37+
38+
export interface Expressions {
39+
iEnum: (IntEnum.First | IntEnum.Second | IntEnum.Third);
40+
nested: {
41+
nestedEnum: StringEnum.A;
42+
nestedUnionEnum: (StringEnum.A | StringEnum.B);
43+
};
44+
normal: StringEnum.A;
45+
sEnum: (StringEnum.A | StringEnum.B);
46+
sEnumMem: (StringEnumWithMemberTypes.First | StringEnumWithMemberTypes.Second);
47+
}

0 commit comments

Comments
 (0)