// Copyright 2023 CUE Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package runtime import ( "iter" "cuelang.org/go/cue/ast" "cuelang.org/go/cue/build" "cuelang.org/go/cue/errors" "cuelang.org/go/cue/token" "cuelang.org/go/internal" "cuelang.org/go/internal/core/adt" "cuelang.org/go/internal/core/walk" ) // AddInjection sets an injection value to be used for injection // of values with an @extern(kind) attribute where kind is i.Kind(). // If there's more than one injection registered for a given kind, // the results from all of them will be unified. func (r *Runtime) AddInjection(i Injection) { if r.injections == nil { r.injections = map[string]Injection{} } switch ij := r.injections[i.Kind()].(type) { case nil: r.injections[i.Kind()] = i case *multiInjection: ij.injections = append(ij.injections, i) default: r.injections[i.Kind()] = &multiInjection{ kind: i.Kind(), injections: []Injection{ij, i}, } } } // Injection defines an entrypoint for creating per-instance injectors. type Injection interface { // InjectorForInstance returns a new injector for the // given build instance. InjectorForInstance(b *build.Instance, r *Runtime) (Injector, errors.Error) // Kind returns the @extern kind for this injection, // for example "embed" for @extern(embed). // A given Injection instance should always return // the same value. Kind() string } // An Injector fills in an adt.Expr for fields marked with `@extern(kind)`. type Injector interface { // InjectedValue returns a value to be unified at the position of // the given external attribute. The scope argument // holds the value of the instance before any injections // have been unified into it. InjectedValue(attr *ExternAttr, scope *adt.Vertex) (adt.Expr, errors.Error) } // InjectImplementations modifies v to include implementations of functions // for fields associated with the @extern attributes. // // TODO(mvdan): unexport again once cue.Instance.Build is no longer used by `cue cmd` // and can be removed entirely. func (r *Runtime) InjectImplementations(b *build.Instance, v *adt.Vertex) (errs errors.Error) { d := &externDecorator{ runtime: r, pkg: b, } for _, f := range b.Files { d.errs = errors.Append(d.errs, d.addFile(f)) } for c := range v.LeafConjuncts() { d.decorateConjunct(c.Elem(), v) } return d.errs } // externDecorator locates extern attributes and calls the relevant injectors // to inject values. type externDecorator struct { runtime *Runtime pkg *build.Instance injectors map[string]Injector // fileKinds maps each AST file to the extern kinds declared in it, // along with their file-level @extern attribute. fileKinds map[*token.File]map[string]*internal.Attr errs errors.Error } // addFile finds injection points in the given ast.File for external // implementations. func (d *externDecorator) addFile(f *ast.File) (errs errors.Error) { kinds, _, err := findExternFileAttrs(f) if err != nil { return err } if len(kinds) == 0 { return nil } if d.fileKinds == nil { d.fileKinds = map[*token.File]map[string]*internal.Attr{} } d.fileKinds[f.Pos().File()] = kinds for kind, attr := range kinds { if err := d.initInjector(kind, attr.Pos); err != nil { errs = errors.Append(errs, err) } } return errs } // findExternFileAttrs reports all extern kinds from file-level @extern(kind) // attributes in f, the position of each corresponding attribute, and f's // declarations from the package directive onwards. It's an error if duplicate // @extern attributes for the same kind are found. decls == nil signals that // this file should be skipped. func findExternFileAttrs(f *ast.File) (kinds map[string]*internal.Attr, decls []ast.Decl, err errors.Error) { var ( hasPkg bool p int fileAttrs []token.Pos ) loop: for ; p < len(f.Decls); p++ { switch a := f.Decls[p].(type) { case *ast.Package: hasPkg = true break loop case *ast.Attribute: if a.Name() != "extern" { continue } attr := internal.ParseAttr(a) fileAttrs = append(fileAttrs, attr.Pos) if attr.Err != nil { err = errors.Append(err, attr.Err) continue } k, e := attr.String(0) if e != nil { // Unreachable. err = errors.Append(err, errors.Newf(attr.Pos, "%s", e)) continue } if k == "" { err = errors.Append(err, errors.Newf(attr.Pos, "injection name must be non-empty")) continue } if kinds == nil { kinds = map[string]*internal.Attr{} } if _, ok := kinds[k]; ok { err = errors.Append(err, errors.Newf(attr.Pos, "duplicate @extern attribute for kind %q", k)) continue } kinds[k] = attr } } switch { case len(fileAttrs) == 0 && !hasPkg: return nil, nil, err case len(fileAttrs) > 0 && !hasPkg: for _, a := range fileAttrs { err = errors.Append(err, errors.Newf(a, "extern attribute without package clause")) } return nil, nil, err case len(fileAttrs) == 0 && hasPkg: // Check that there are no top-level extern attributes. for p++; p < len(f.Decls); p++ { x, ok := f.Decls[p].(*ast.Attribute) if !ok { continue } if key, _ := x.Split(); key == "extern" { err = errors.Append(err, errors.Newf(x.Pos(), "extern attribute must appear before package clause")) } } return nil, nil, err } return kinds, f.Decls[p:], err } // initInjector initializes the injector for kind, if applicable. The pos // argument represents the position of the file-level @extern attribute. func (d *externDecorator) initInjector(kind string, pos token.Pos) errors.Error { if _, ok := d.injectors[kind]; ok { return nil } if d.injectors == nil { d.injectors = map[string]Injector{} } x := d.runtime.injections[kind] if x == nil { return errors.Newf(pos, "no injection defined for %q", kind) } inj, err := x.InjectorForInstance(d.pkg, d.runtime) if err != nil { return err } d.injectors[kind] = inj return nil } type ExternAttrs struct { // TopLevel holds all the extern attributes declared // before the package directive, e.g. @extern(embed) TopLevel map[string]*internal.Attr // Body holds a sequence of (node, attribute) pairs // corresponding to all the extern attributes in the body // of the file. Body iter.Seq[ExternAttr] } type ExternAttr struct { // TopLevel holds the top level @extern attribute for the attribute, for example @extern(embed). // This is the same as ExternAttrs.TopLevel[TopLevel.Name]. TopLevel *internal.Attr // Parent holds the parent AST node that contains the attribute. // It's either a *ast.Field, *ast.StructLit, or *ast.File. Parent ast.Node // Attr holds the extern attribute itself. Attr *internal.Attr } func ExternAttrsForFile(file *ast.File) (*ExternAttrs, errors.Error) { kinds, decls, err := findExternFileAttrs(file) if err != nil { return nil, err } return &ExternAttrs{ TopLevel: kinds, Body: func(yield func(ExternAttr) bool) { if len(kinds) > 0 { walkExternFileAttrs(file, decls, kinds, yield) } }, }, nil } func walkExternFileAttrs(file *ast.File, decls []ast.Decl, kinds map[string]*internal.Attr, yield func(ExternAttr) bool) { ast.Walk(&ast.File{Decls: decls}, func(n ast.Node) bool { var elts []ast.Decl parent := n switch n := n.(type) { case *ast.StructLit: elts = n.Elts case *ast.File: parent = file elts = n.Decls default: return true } for _, elt := range elts { switch elt := elt.(type) { case *ast.Attribute: if !yieldAttr(elt, parent, kinds, yield) { return false } case *ast.Field: for _, attr := range elt.Attrs { if !yieldAttr(attr, elt, kinds, yield) { return false } } } } return true }, nil) } func yieldAttr(attr *ast.Attribute, parent ast.Node, kinds map[string]*internal.Attr, yield func(ExternAttr) bool) bool { toplevel := kinds[attr.Name()] if toplevel == nil { return true } return yield(ExternAttr{ TopLevel: toplevel, Parent: parent, Attr: internal.ParseAttr(attr), }) } func (d *externDecorator) decorateConjunct(e adt.Elem, scope *adt.Vertex) { w := walk.Visitor{ Before: func(n adt.Node) bool { if s, ok := n.(*adt.StructLit); ok { // Only walk the tree for struct literals that // are from a file with some extern declarations. return s.Src != nil && len(d.fileKinds[s.Src.Pos().File()]) > 0 } return true }, After: func(n adt.Node) { d.processNode(n, scope) }, } w.Elem(e) } // processNode processes a single adt Node; if it's a struct literal, // it decorates both embedded and field-level attributes. func (d *externDecorator) processNode(n adt.Node, scope *adt.Vertex) { s, ok := n.(*adt.StructLit) if !ok { return } kinds := d.fileKinds[s.Src.Pos().File()] // Process attributes on fields. for _, decl := range s.Decls { var valuePtr *adt.Expr switch decl := decl.(type) { case *adt.Field: valuePtr = &decl.Value case *adt.BulkOptionalField: valuePtr = &decl.Value case *adt.DynamicField: valuePtr = &decl.Value default: continue } srcField := decl.Source().(*ast.Field) // We know all the above types come from ast.Field. for _, attr := range srcField.Attrs { if expr := d.injectedValue(attr, srcField, kinds, scope); expr != nil { *valuePtr = &adt.BinaryExpr{ Op: adt.AndOp, X: *valuePtr, Y: expr, } } } } // Process embedded attributes. var srcParent ast.Node var srcDecls []ast.Decl switch src := s.Src.(type) { case *ast.File: srcParent = src srcDecls = src.Decls case *ast.StructLit: srcParent = src srcDecls = src.Elts default: panic("unexpected type in adt.StructLit.Src") } for _, decl := range srcDecls { if attr, ok := decl.(*ast.Attribute); ok { if expr := d.injectedValue(attr, srcParent, kinds, scope); expr != nil { s.Decls = append(s.Decls, expr) } } } } func (d *externDecorator) injectedValue(astAttr *ast.Attribute, parent ast.Node, kinds map[string]*internal.Attr, scope *adt.Vertex) adt.Expr { topLevel := kinds[astAttr.Name()] if topLevel == nil { return nil } attr := internal.ParseAttr(astAttr) if attr.Err != nil { d.errs = errors.Append(d.errs, attr.Err) return nil } inj := d.injectors[attr.Name] if inj == nil { return nil } ea := &ExternAttr{ TopLevel: topLevel, Parent: parent, Attr: attr, } b, err := inj.InjectedValue(ea, scope) if err != nil { d.errs = errors.Append(d.errs, errors.Wrap(errors.Newf(attr.Pos, "@%s", attr.Name), err)) return nil } return b } // multiInjection combines multiple [Injection] implementations // into a single implementation that unifies the results from all of them. type multiInjection struct { kind string injections []Injection } func (ij *multiInjection) Kind() string { return ij.kind } func (ij *multiInjection) InjectorForInstance(b *build.Instance, r *Runtime) (Injector, errors.Error) { var injectors multiInjector var errs errors.Error for _, inj := range ij.injections { injector, err := inj.InjectorForInstance(b, r) if err != nil { errs = errors.Append(errs, err) continue } injectors = append(injectors, injector) } if errs != nil { return nil, errs } return injectors, nil } type multiInjector []Injector func (ij multiInjector) InjectedValue(attr *ExternAttr, scope *adt.Vertex) (adt.Expr, errors.Error) { var expr adt.Expr var errs errors.Error for _, inj := range ij { e, err := inj.InjectedValue(attr, scope) if err != nil { errs = errors.Append(errs, err) continue } if expr == nil { expr = e } else { expr = &adt.BinaryExpr{ Op: adt.AndOp, X: expr, Y: e, } } } if errs != nil { return nil, errs } return expr, nil }