Skip to content

Commit d567750

Browse files
committed
cue/load: pass language version down to parser
In order to pass the language version down to the parser, we need to make some API-breaking changes; specifically: - `cue/load.Config.ParseFile` gains a `parser.Config` argument - so does the function passed to `cue/build.ParseFile`. - as does the `module.ReadCUEFS.ReadCUEFile` method This now enables arbitrary parser options to be passed down, not just the language version. It does mean that the CUE file cache is now keyed by parser configuration, which will probably result in more work being done, but may also result in less overall memory usage when the only work needing to be done for a file is to read its imports. We also remove support for `CUE_SYNTAX_OVERRIDE` which had no effect anyway, since `parser.FromVersion` hasn't done anything for a while now. We also add an `ast.File.LanguageVersion` field so that it's easy to find out which language version was used to parse the file. In the future, we may also add it to `token.File`, so it's available in all position information. It might also be removed. Signed-off-by: Roger Peppe <[email protected]> Change-Id: I7b52184b08f028909ad415005d023d60a79b8937 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1217124 Reviewed-by: Marcel van Lohuizen <[email protected]> TryBot-Result: CUEcueckoo <[email protected]> Unity-Result: CUE porcuepine <[email protected]>
1 parent 5a04616 commit d567750

File tree

28 files changed

+189
-66
lines changed

28 files changed

+189
-66
lines changed

cmd/cue/cmd/common.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,12 @@ func defaultConfig() (*config, error) {
4444
}
4545
return &config{
4646
loadCfg: &load.Config{
47-
ParseFile: func(name string, src interface{}) (*ast.File, error) {
48-
options := []parser.Option{
49-
parser.ParseComments,
50-
}
47+
ParseFile: func(name string, src interface{}, cfg parser.Config) (*ast.File, error) {
5148
cuedebug.Init()
5249
if cuedebug.Flags.ParserTrace {
53-
options = append(options, parser.Trace)
50+
cfg = cfg.Apply(parser.Trace)
5451
}
55-
return parser.ParseFile(name, src, options...)
52+
return parser.ParseFile(name, src, cfg)
5653
},
5754
Registry: reg,
5855
},

cmd/cue/cmd/testdata/script/issue3282.txtar

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
! exec cue export
55
cmp stderr want-stderr
66
-- want-stderr --
7-
test.example@v0: import failed: cannot find package "test.example/blah": cannot get imports: cannot read "blah/invalid.cue": missing ',' in struct literal:
7+
import failed: missing ',' in struct literal:
88
./foo.cue:2:8
9+
./blah/invalid.cue:3:11
910
-- cue.mod/module.cue --
1011
module: "test.example"
1112
language: version: "v0.9.0"

cue/ast/ast.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,13 @@ type File struct {
954954
Imports []*ImportSpec // imports in this file
955955
Unresolved []*Ident // unresolved identifiers in this file
956956

957+
// TODO remove this field: it's here as a temporary
958+
// entity so that tests can determine which version
959+
// the file was parsed with. A better approach is probably to
960+
// include the language version in the `token.File` so
961+
// it's available in every Position.
962+
LanguageVersion string // The language version as configured by [parser.ParseFile].
963+
957964
comments
958965
}
959966

cue/build/context.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ package build
2727

2828
import (
2929
"cuelang.org/go/cue/ast"
30+
"cuelang.org/go/cue/parser"
3031
)
3132

3233
// A Context keeps track of state of building instances and caches work.
3334
type Context struct {
3435
loader LoadFunc
35-
parseFunc func(str string, src interface{}) (*ast.File, error)
36+
parseFunc func(str string, src interface{}, cfg parser.Config) (*ast.File, error)
3637

3738
initialized bool
3839

@@ -119,6 +120,9 @@ func Loader(f LoadFunc) Option {
119120
// to change the effective file contents or the behavior of the parser,
120121
// or to modify the syntax tree. For example, changing the backwards
121122
// compatibility.
122-
func ParseFile(f func(filename string, src interface{}) (*ast.File, error)) Option {
123+
//
124+
// In general, the function should respect the parser configuration passed
125+
// in, and modify it incrementally rather than overwriting it entirely.
126+
func ParseFile(f func(filename string, src interface{}, cfg parser.Config) (*ast.File, error)) Option {
123127
return func(c *Context) { c.parseFunc = f }
124128
}

cue/build/instance.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,14 @@ func (inst *Instance) Context() *Context {
182182
}
183183

184184
func (inst *Instance) parse(name string, src interface{}) (*ast.File, error) {
185+
cfg := parser.NewConfig(parser.ParseComments)
186+
if inst.ModuleFile != nil && inst.ModuleFile.Language != nil {
187+
cfg = cfg.Apply(parser.Version(inst.ModuleFile.Language.Version))
188+
}
185189
if inst.ctxt != nil && inst.ctxt.parseFunc != nil {
186-
return inst.ctxt.parseFunc(name, src)
190+
return inst.ctxt.parseFunc(name, src, cfg)
187191
}
188-
return parser.ParseFile(name, src, parser.ParseComments)
192+
return parser.ParseFile(name, src, cfg)
189193
}
190194

191195
// LookupImport defines a mapping from an ImportSpec's ImportPath to Instance.

cue/interpreter/wasm/exe_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,9 @@ func dirInstance(t *testing.T, name string) *build.Instance {
134134
return inst
135135
}
136136

137-
func loadFile(filename string, src any) (*ast.File, error) {
138-
return parser.ParseFile(filename, src, parser.ParseFuncs)
137+
func loadFile(filename string, src any, cfg parser.Config) (*ast.File, error) {
138+
cfg = cfg.Apply(parser.ParseFuncs)
139+
return parser.ParseFile(filename, src, cfg)
139140
}
140141

141142
func must[T any](v T, err error) func(t *testing.T) T {

cue/load/config.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"cuelang.org/go/cue/ast"
2525
"cuelang.org/go/cue/build"
2626
"cuelang.org/go/cue/errors"
27+
"cuelang.org/go/cue/parser"
2728
"cuelang.org/go/cue/token"
2829
"cuelang.org/go/internal"
2930
"cuelang.org/go/mod/modconfig"
@@ -149,6 +150,11 @@ type Config struct {
149150
// equal to Module.
150151
modFile *modfile.File
151152

153+
// parserConfig holds the configuration that will be passed
154+
// when parsing CUE files. It includes the version from
155+
// the module file.
156+
parserConfig parser.Config
157+
152158
// Package defines the name of the package to be loaded. If this is not set,
153159
// the package must be uniquely defined from its context. Special values:
154160
// _ load files without a package
@@ -266,7 +272,7 @@ type Config struct {
266272
// An application may supply a custom implementation of ParseFile to change
267273
// the effective file contents or the behavior of the parser, or to modify
268274
// the syntax tree.
269-
ParseFile func(name string, src interface{}) (*ast.File, error)
275+
ParseFile func(name string, src interface{}, cfg parser.Config) (*ast.File, error)
270276

271277
// Overlay provides a mapping of absolute file paths to file contents. If
272278
// the file with the given path already exists, the parser will use the
@@ -382,12 +388,20 @@ func (c Config) complete() (cfg *Config, err error) {
382388
}
383389
c.Registry = registry
384390
}
391+
c.parserConfig = parser.NewConfig(parser.ParseComments)
385392
if err := c.loadModule(); err != nil {
386393
return nil, err
387394
}
388395
return &c, nil
389396
}
390397

398+
func (c *Config) languageVersion() string {
399+
if c.modFile == nil || c.modFile.Language == nil {
400+
return ""
401+
}
402+
return c.modFile.Language.Version
403+
}
404+
391405
// loadModule loads the module file, resolves and downloads module
392406
// dependencies. It sets c.Module if it's empty or checks it for
393407
// consistency with the module file otherwise.
@@ -438,6 +452,8 @@ func (c *Config) loadModule() error {
438452
return errors.Newf(token.NoPos, "inconsistent modules: got %q, want %q", mf.Module, c.Module)
439453
}
440454
c.Module = mf.QualifiedModule()
455+
// Set the default version for CUE files without a module.
456+
c.parserConfig = c.parserConfig.Apply(parser.Version(c.modFile.Language.Version))
441457
return nil
442458
}
443459

cue/load/fs.go

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"cuelang.org/go/cue/build"
3333
"cuelang.org/go/cue/cuecontext"
3434
"cuelang.org/go/cue/errors"
35+
"cuelang.org/go/cue/parser"
3536
"cuelang.org/go/cue/token"
3637
"cuelang.org/go/internal/encoding"
3738
"cuelang.org/go/mod/module"
@@ -86,10 +87,11 @@ func (fs *fileSystem) getDir(dir string, create bool) map[string]*overlayFile {
8687
// the resulting source locations back to the filesystem
8788
// paths required by most of the `cue/load` package
8889
// implementation.
89-
func (fs *fileSystem) ioFS(root string) iofs.FS {
90+
func (fs *fileSystem) ioFS(root string, languageVersion string) iofs.FS {
9091
return &ioFS{
91-
fs: fs,
92-
root: root,
92+
fs: fs,
93+
root: root,
94+
languageVersion: languageVersion,
9395
}
9496
}
9597

@@ -281,8 +283,9 @@ var _ interface {
281283
} = (*ioFS)(nil)
282284

283285
type ioFS struct {
284-
fs *fileSystem
285-
root string
286+
fs *fileSystem
287+
root string
288+
languageVersion string
286289
}
287290

288291
func (fs *ioFS) OSRoot() string {
@@ -345,14 +348,15 @@ var _ module.ReadCUEFS = (*ioFS)(nil)
345348
// reading and updating the syntax file cache, which
346349
// is shared with the cache used by the [fileSystem.getCUESyntax]
347350
// method.
348-
func (fs *ioFS) ReadCUEFile(path string) (*ast.File, error) {
351+
func (fs *ioFS) ReadCUEFile(path string, cfg parser.Config) (*ast.File, error) {
349352
fpath, err := fs.absPathFromFSPath(path)
350353
if err != nil {
351354
return nil, err
352355
}
356+
key := fileCacheKey{cfg, fpath}
353357
cache := fs.fs.fileCache
354358
cache.mu.Lock()
355-
entry, ok := cache.entries[fpath]
359+
entry, ok := cache.entries[key]
356360
cache.mu.Unlock()
357361
if ok {
358362
return entry.file, entry.err
@@ -370,16 +374,19 @@ func (fs *ioFS) ReadCUEFile(path string) (*ast.File, error) {
370374
if err != nil {
371375
cache.mu.Lock()
372376
defer cache.mu.Unlock()
373-
cache.entries[fpath] = fileCacheEntry{nil, err}
377+
cache.entries[key] = fileCacheEntry{nil, err}
374378
return nil, err
375379
}
376380
}
381+
if fs.languageVersion != "" {
382+
cfg = cfg.Apply(parser.Version(fs.languageVersion))
383+
}
377384
return fs.fs.getCUESyntax(&build.File{
378385
Filename: fpath,
379386
Encoding: build.CUE,
380387
// Form: build.Schema,
381388
Source: data,
382-
})
389+
}, cfg)
383390
}
384391

385392
// ioFSFile implements [io/fs.File] for the overlay filesystem.
@@ -433,26 +440,29 @@ func (f *ioFSFile) ReadDir(n int) ([]iofs.DirEntry, error) {
433440
return entries, err
434441
}
435442

436-
func (fs *fileSystem) getCUESyntax(bf *build.File) (*ast.File, error) {
443+
func (fs *fileSystem) getCUESyntax(bf *build.File, cfg parser.Config) (*ast.File, error) {
437444
fs.fileCache.mu.Lock()
438445
defer fs.fileCache.mu.Unlock()
439446
if bf.Encoding != build.CUE {
440447
panic("getCUESyntax called with non-CUE file encoding")
441448
}
449+
key := fileCacheKey{cfg, bf.Filename}
442450
// When it's a regular CUE file with no funny stuff going on, we
443451
// check and update the syntax cache.
444452
useCache := bf.Form == "" && bf.Interpretation == ""
445453
if useCache {
446-
if syntax, ok := fs.fileCache.entries[bf.Filename]; ok {
454+
if syntax, ok := fs.fileCache.entries[key]; ok {
447455
return syntax.file, syntax.err
448456
}
449457
}
450-
d := encoding.NewDecoder(fs.fileCache.ctx, bf, &fs.fileCache.config)
458+
encodingCfg := fs.fileCache.config
459+
encodingCfg.ParserConfig = cfg
460+
d := encoding.NewDecoder(fs.fileCache.ctx, bf, &encodingCfg)
451461
defer d.Close()
452462
// Note: CUE files can never have multiple file parts.
453463
f, err := d.File(), d.Err()
454464
if useCache {
455-
fs.fileCache.entries[bf.Filename] = fileCacheEntry{f, err}
465+
fs.fileCache.entries[key] = fileCacheEntry{f, err}
456466
}
457467
return f, err
458468
}
@@ -465,7 +475,7 @@ func newFileCache(c *Config) *fileCache {
465475
ParseFile: c.ParseFile,
466476
},
467477
ctx: cuecontext.New(),
468-
entries: make(map[string]fileCacheEntry),
478+
entries: make(map[fileCacheKey]fileCacheEntry),
469479
}
470480
}
471481

@@ -474,7 +484,12 @@ type fileCache struct {
474484
config encoding.Config
475485
ctx *cue.Context
476486
mu sync.Mutex
477-
entries map[string]fileCacheEntry
487+
entries map[fileCacheKey]fileCacheEntry
488+
}
489+
490+
type fileCacheKey struct {
491+
cfg parser.Config
492+
path string
478493
}
479494

480495
type fileCacheEntry struct {

cue/load/fs_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func TestIOFS(t *testing.T) {
3737
Overlay: overlay,
3838
})
3939
qt.Assert(t, qt.IsNil(err))
40-
ffsys := fsys.ioFS(dir)
40+
ffsys := fsys.ioFS(dir, "v0.12.0")
4141
err = fstest.TestFS(ffsys, append(slices.Clip(onDiskFiles), overlayFiles...)...)
4242
qt.Assert(t, qt.IsNil(err))
4343
checked := make(map[string]bool)

cue/load/instances.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626

2727
"cuelang.org/go/cue/ast"
2828
"cuelang.org/go/cue/build"
29+
"cuelang.org/go/cue/parser"
2930
"cuelang.org/go/internal/filetypes"
3031
"cuelang.org/go/internal/mod/modimports"
3132
"cuelang.org/go/internal/mod/modload"
@@ -247,7 +248,8 @@ func loadPackagesFromArgs(
247248
// not a CUE file; assume it has no imports for now.
248249
continue
249250
}
250-
syntax, err := cfg.fileSystem.getCUESyntax(f)
251+
// Note: this gets the current module's language version if there is one.
252+
syntax, err := cfg.fileSystem.getCUESyntax(f, cfg.parserConfig.Apply(parser.ImportsOnly))
251253
if err != nil {
252254
return nil, fmt.Errorf("cannot get syntax for %q: %w", f.Filename, err)
253255
}
@@ -264,7 +266,7 @@ func loadPackagesFromArgs(
264266
}
265267
return loadPackages(ctx, cfg, cfg.modFile,
266268
module.SourceLoc{
267-
FS: cfg.fileSystem.ioFS(cfg.ModuleRoot),
269+
FS: cfg.fileSystem.ioFS(cfg.ModuleRoot, cfg.modFile.Language.Version),
268270
Dir: ".",
269271
},
270272
slices.Sorted(maps.Keys(pkgPaths)),

0 commit comments

Comments
 (0)