this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

internal/encoding/gotypes: better support for CUE versus Go package names

Given one source directory, we might have CUE and Go packages
with different names, so we can't naively generate a Go package
with the same name as the original CUE package in the general case.

Worse, one directory can contain multiple CUE packages but only
one Go package, so we cannot generate multiple Go packages one-to-one.

Support package-level `@go()` attributes to tell the generator
which Go package name to use instead of the default naive behavior.

Note that we also need to move away from the naive cue_gen.go filenames,
as that causes conflicts when multiple CUE packages share one directory.
Use cue_types_${cuepkgname}.go instead.

Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
Change-Id: Idc42aa70de3a1223451e6dc9289a7cebd33f465b
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1207310
Reviewed-by: Roger Peppe <rogpeppe@gmail.com>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>

+91 -19
+48 -12
cmd/cue/cmd/testdata/script/exp_gengotypes.txtar
··· 3 3 4 4 # Check how many files were generated, and see that it aligns with how many files we expect. 5 5 find-files . 6 - stdout -count=3 'cue_gen.go$' 7 - stdout -count=3 'cue_gen.go.want$' 6 + stdout -count=5 'cue_types_.*gen\.go$' 7 + stdout -count=5 'cue_types_.*gen\.go.want$' 8 8 # No Go file is generated for imported itself, as it's only loaded as part of subpackage instances. 9 9 ! stdout imported${/@R}gen_go.cue 10 10 # No bad or unused packages should have been generated at all. ··· 12 12 ! stdout 'unused.*'${/@R}gen_go.cue 13 13 14 14 # Check the contents of the generated files. 15 - cmp root/cue_gen.go root/cue_gen.go.want 16 - cmp imported/subinst/cue_gen.go imported/subinst/cue_gen.go.want 17 - cmp imported/indirect/cue_gen.go imported/indirect/cue_gen.go.want 15 + cmp root/cue_types_gen.go root/cue_types_gen.go 16 + cmp imported/subinst/cue_types_imported_gen.go imported/subinst/cue_types_imported_gen.go 17 + cmp imported/indirect/cue_types_gen.go imported/indirect/cue_types_gen.go 18 + cmp imported/multipkg/cue_types_multipkg_one_gen.go imported/multipkg/cue_types_multipkg_one_gen.go.want 19 + cmp imported/multipkg/cue_types_multipkg_two_gen.go imported/multipkg/cue_types_multipkg_two_gen.go.want 18 20 19 21 # Check various properties about the generated code. 20 22 21 23 # Names including "neverGenerate" signal that they must not be generated. 22 - ! grep '(?i)nevergenerate' root/cue_gen.go 24 + ! grep '(?i)nevergenerate' root/cue_types_gen.go 23 25 # Hidden definitions must never result in exported Go types. 24 - ! grep '^type Hidden' root/cue_gen.go 26 + ! grep '^type Hidden' root/cue_types_gen.go 25 27 # We use '_' as a separator for nested definitions; check it's not misused. 26 - ! grep '^type _' root/cue_gen.go 27 - ! grep '__' root/cue_gen.go 28 + ! grep '^type _' root/cue_types_gen.go 29 + ! grep '__' root/cue_types_gen.go 28 30 # TODO: ensure that no IncompleteKind TODOs are left in the output. 29 31 30 32 # The resulting Go should all build without error. ··· 564 566 package root 565 567 566 568 import ( 569 + "foo.test/bar/imported/multipkg:multipkg_one" 570 + "foo.test/bar/imported/multipkg:multipkg_two" 567 571 "foo.test/bar/imported/subinst:imported" 568 572 "foo.test/bar/imported/unused" 569 573 ) ··· 575 579 UpperRegular?: imported.UpperRegular 576 580 lowerDef?: imported.#lowerDef 577 581 UpperDef?: imported.#UpperDef 582 + 583 + multiOne?: multipkg_one.#One 584 + multiTwo?: multipkg_two.#Two 578 585 } 579 586 580 587 _unusedImport: unused.#UnusedNeverGenerate 581 - -- root/cue_gen.go.want -- 588 + -- root/cue_types_gen.go.want -- 582 589 // Code generated by "cue exp gengotypes"; DO NOT EDIT. 583 590 584 591 package root 585 592 586 593 import ( 594 + "foo.test/bar/imported/multipkg" 587 595 "foo.test/bar/imported/subinst" 588 596 "go/constant" 589 597 "go/token" ··· 600 608 LowerDef imported.LowerDef `json:"lowerDef,omitempty"` 601 609 602 610 UpperDef imported.UpperDef `json:"UpperDef,omitempty"` 611 + 612 + MultiOne multipkg_one.One `json:"multiOne,omitempty"` 613 + 614 + MultiTwo multipkg_two.Two `json:"multiTwo,omitempty"` 603 615 } 604 616 605 617 type EmptyStruct struct { ··· 790 802 UpperRegular: int 791 803 #lowerDef: int 792 804 #UpperDef: int 793 - -- imported/subinst/cue_gen.go.want -- 805 + -- imported/subinst/cue_types_imported_gen.go.want -- 794 806 // Code generated by "cue exp gengotypes"; DO NOT EDIT. 795 807 796 808 package imported ··· 812 824 package indirect 813 825 814 826 #Indirect: int 815 - -- imported/indirect/cue_gen.go.want -- 827 + -- imported/indirect/cue_types_gen.go.want -- 816 828 // Code generated by "cue exp gengotypes"; DO NOT EDIT. 817 829 818 830 package indirect ··· 823 835 824 836 // This API and package are not used as part of the generated schemas. 825 837 #UnusedNeverGenerate: int 838 + -- imported/multipkg/one.cue -- 839 + package multipkg_one 840 + 841 + @go(multipkg) 842 + 843 + #One: int 844 + -- imported/multipkg/two.cue -- 845 + package multipkg_two 846 + 847 + @go(multipkg) 848 + 849 + #Two: int 850 + -- imported/multipkg/cue_types_multipkg_one_gen.go.want -- 851 + // Code generated by "cue exp gengotypes"; DO NOT EDIT. 852 + 853 + package multipkg 854 + 855 + type One int64 856 + -- imported/multipkg/cue_types_multipkg_two_gen.go.want -- 857 + // Code generated by "cue exp gengotypes"; DO NOT EDIT. 858 + 859 + package multipkg 860 + 861 + type Two int64 826 862 -- bad_syntax/invalid.cue -- 827 863 package bad_syntax 828 864
+43 -7
internal/encoding/gotypes/generate.go
··· 36 36 // record which package instances have already been generated 37 37 instDone := make(map[*build.Instance]bool) 38 38 39 + goPkgNamesDoneByDir := make(map[string]string) 40 + 39 41 // ensure we don't modify the parameter slice 40 42 insts = slices.Clip(insts) 41 43 for len(insts) > 0 { // we append imports to this list ··· 101 103 } 102 104 103 105 g.appendf("// Code generated by \"cue exp gengotypes\"; DO NOT EDIT.\n\n") 104 - g.appendf("package %s\n\n", inst.PkgName) 106 + goPkgName := goPkgNameForInstance(inst, instVal) 107 + if prev, ok := goPkgNamesDoneByDir[inst.Dir]; ok && prev != goPkgName { 108 + return fmt.Errorf("cannot generate two Go packages in one directory; %s and %s", prev, goPkgName) 109 + } else { 110 + goPkgNamesDoneByDir[inst.Dir] = goPkgName 111 + } 112 + g.appendf("package %s\n\n", goPkgName) 105 113 // TODO: imported := slices.Sorted(maps.Values(g.importedAs)) 106 114 var imported []string 107 115 for _, path := range g.importedAs { ··· 117 125 g.appendf(")\n") 118 126 } 119 127 g.appendf("%s", typesBuf) 128 + // The generated file is named after the CUE package, not the generated Go package, 129 + // as we can have multiple CUE packages in one directory all generating to one Go package. 130 + // To keep the filename short for common cases, if we are generating a CUE package 131 + // whose package name is implied from its import path, omit the package name element. 132 + basename := "cue_types_gen.go" 133 + ip := module.ParseImportPath(inst.ImportPath) 134 + ip1 := ip 135 + ip1.Qualifier = "" 136 + ip1.ExplicitQualifier = false 137 + ip1 = module.ParseImportPath(ip1.String()) 138 + if ip.Qualifier != ip1.Qualifier { 139 + basename = fmt.Sprintf("cue_types_%s_gen.go", inst.PkgName) 140 + } 141 + outpath := filepath.Join(inst.Dir, basename) 142 + 120 143 formatted, err := goformat.Source(g.dst) 121 144 if err != nil { 122 145 // Showing the generated Go code helps debug where the syntax error is. ··· 126 149 for i, line := range lines { 127 150 withLineNums = fmt.Appendf(withLineNums, "% 4d: %s\n", i+1, line) 128 151 } 129 - fmt.Fprintf(os.Stderr, "-- %s/cue_gen.go --\n%s\n--\n", inst.Dir, withLineNums) 152 + fmt.Fprintf(os.Stderr, "-- %s --\n%s\n--\n", filepath.ToSlash(outpath), withLineNums) 130 153 return err 131 154 } 132 - // TODO: this won't work for multi-package directories. 133 - outfile := filepath.Join(inst.Dir, "cue_gen.go") 134 - if err := os.WriteFile(outfile, formatted, 0o666); err != nil { 155 + if err := os.WriteFile(outpath, formatted, 0o666); err != nil { 135 156 return err 136 157 } 137 158 } ··· 276 297 if optional { 277 298 omitEmpty = ",omitempty" 278 299 } 279 - g.appendf(" `json:\"%s%s\"`\n\n", cueName, omitEmpty) 300 + g.appendf(" `json:\"%s%s\"`", cueName, omitEmpty) 280 301 g.appendf("\n\n") 281 302 } 282 303 g.appendf("}") ··· 369 390 return name 370 391 } 371 392 393 + // goPkgNameForInstance determines what to name a Go package generated from a CUE instance. 394 + // By default this is the CUE package name, but it can be overriden by a @go() package attribute. 395 + func goPkgNameForInstance(inst *build.Instance, instVal cue.Value) string { 396 + attrs := instVal.Attributes(cue.DeclAttr) 397 + for _, attr := range attrs { 398 + if attr.Name() == "go" { 399 + if s, _ := attr.String(0); s != "" { 400 + return s 401 + } 402 + break 403 + } 404 + } 405 + return inst.PkgName 406 + } 407 + 372 408 // emitTypeReference attempts to generate a CUE value as a Go type via a reference, 373 409 // either to a type in the same Go package, or to a type in an imported package. 374 410 func (g *generator) emitTypeReference(val cue.Value, optional bool) bool { ··· 390 426 sb.WriteString("*") 391 427 } 392 428 if root != g.pkgRoot { 393 - sb.WriteString(inst.PkgName) 429 + sb.WriteString(goPkgNameForInstance(inst, root)) 394 430 sb.WriteString(".") 395 431 } 396 432