mirror of https://github.com/pulumi/pulumi.git
314 lines
9.7 KiB
Go
314 lines
9.7 KiB
Go
// Copyright 2016-2020, Pulumi Corporation.
|
|
//
|
|
// 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 model
|
|
|
|
import (
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
// Definition represents a single definition in a Scope.
|
|
type Definition interface {
|
|
Traversable
|
|
|
|
SyntaxNode() hclsyntax.Node
|
|
}
|
|
|
|
// A Keyword is a non-traversable definition that allows scope traversals to bind to arbitrary keywords.
|
|
type Keyword string
|
|
|
|
// Traverse attempts to traverse the keyword, and always fails.
|
|
func (kw Keyword) Traverse(traverser hcl.Traverser) (Traversable, hcl.Diagnostics) {
|
|
return DynamicType, hcl.Diagnostics{cannotTraverseKeyword(string(kw), traverser.SourceRange())}
|
|
}
|
|
|
|
// SyntaxNode returns the syntax node for the keyword, which is always syntax.None.
|
|
func (kw Keyword) SyntaxNode() hclsyntax.Node {
|
|
return syntax.None
|
|
}
|
|
|
|
// A Variable is a traversable, typed definition that represents a named value.
|
|
type Variable struct {
|
|
// The syntax node associated with the variable definition, if any.
|
|
Syntax hclsyntax.Node
|
|
|
|
// The name of the variable.
|
|
Name string
|
|
// The type of the variable.
|
|
VariableType Type
|
|
}
|
|
|
|
// Traverse attempts to traverse the variable's type.
|
|
func (v *Variable) Traverse(traverser hcl.Traverser) (Traversable, hcl.Diagnostics) {
|
|
return v.VariableType.Traverse(traverser)
|
|
}
|
|
|
|
// SyntaxNode returns the variable's syntax node or syntax.None.
|
|
func (v *Variable) SyntaxNode() hclsyntax.Node {
|
|
return syntaxOrNone(v.Syntax)
|
|
}
|
|
|
|
// Type returns the type of the variable.
|
|
func (v *Variable) Type() Type {
|
|
return v.VariableType
|
|
}
|
|
|
|
func (v *Variable) Value(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
if value, hasValue := context.Variables[v.Name]; hasValue {
|
|
return value, nil
|
|
}
|
|
return cty.DynamicVal, nil
|
|
}
|
|
|
|
// A Constant is a traversable, typed definition that represents a named constant.
|
|
type Constant struct {
|
|
// The syntax node associated with the constant definition, if any.
|
|
Syntax hclsyntax.Node
|
|
|
|
// The name of the constant.
|
|
Name string
|
|
// The value of the constant.
|
|
ConstantValue cty.Value
|
|
|
|
typ Type
|
|
}
|
|
|
|
// Traverse attempts to traverse the constant's value.
|
|
func (c *Constant) Traverse(traverser hcl.Traverser) (Traversable, hcl.Diagnostics) {
|
|
v, diags := traverser.TraversalStep(c.ConstantValue)
|
|
return &Constant{ConstantValue: v}, diags
|
|
}
|
|
|
|
// SyntaxNode returns the constant's syntax node or syntax.None.
|
|
func (c *Constant) SyntaxNode() hclsyntax.Node {
|
|
return syntaxOrNone(c.Syntax)
|
|
}
|
|
|
|
// Type returns the type of the constant.
|
|
func (c *Constant) Type() Type {
|
|
if c.typ == nil {
|
|
if c.ConstantValue.IsNull() {
|
|
c.typ = NoneType
|
|
} else {
|
|
c.typ = ctyTypeToType(c.ConstantValue.Type(), false)
|
|
}
|
|
}
|
|
return c.typ
|
|
}
|
|
|
|
func (c *Constant) Value(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
return c.ConstantValue, nil
|
|
}
|
|
|
|
// A Scope is used to map names to definitions during expression binding.
|
|
//
|
|
// A scope has three namespaces:
|
|
// - one that is exclusive to functions
|
|
// - one that is exclusive to output variables
|
|
// - and one that contains config variables, local variables and resource definitions.
|
|
//
|
|
// When binding a reference, we check `defs` and `outputs`. When binding a function, we check `functions`.
|
|
// Definitions within a namespace such as `defs`, `outputs` or `functions` are expected to have a unique identifier
|
|
// and cannot be redeclared.
|
|
type Scope struct {
|
|
parent *Scope
|
|
syntax hclsyntax.Node
|
|
defs map[string]Definition
|
|
outputs map[string]Definition
|
|
functions map[string]*Function
|
|
}
|
|
|
|
// NewRootScope returns a new unparented scope associated with the given syntax node.
|
|
func NewRootScope(syntax hclsyntax.Node) *Scope {
|
|
return &Scope{
|
|
syntax: syntax,
|
|
defs: map[string]Definition{},
|
|
outputs: map[string]Definition{},
|
|
functions: map[string]*Function{},
|
|
}
|
|
}
|
|
|
|
// Traverse attempts to traverse the scope using the given traverser. If the traverser is a literal string that refers
|
|
// to a name defined within the scope or one of its ancestors, the traversal returns the corresponding definition.
|
|
func (s *Scope) Traverse(traverser hcl.Traverser) (Traversable, hcl.Diagnostics) {
|
|
name, nameType := GetTraverserKey(traverser)
|
|
if nameType != StringType {
|
|
// TODO(pdg): return a better error here
|
|
return DynamicType, hcl.Diagnostics{undefinedVariable("", traverser.SourceRange(), false)}
|
|
}
|
|
|
|
memberName := name.AsString()
|
|
member, hasMember := s.BindReference(memberName)
|
|
if !hasMember {
|
|
return DynamicType, hcl.Diagnostics{undefinedVariable(memberName, traverser.SourceRange(), false)}
|
|
}
|
|
return member, nil
|
|
}
|
|
|
|
// SyntaxNode returns the syntax node associated with the scope, if any.
|
|
func (s *Scope) SyntaxNode() hclsyntax.Node {
|
|
if s != nil {
|
|
return syntaxOrNone(s.syntax)
|
|
}
|
|
return syntax.None
|
|
}
|
|
|
|
// BindReference returns the definition that corresponds to the given name, if any. Each parent scope is checked until
|
|
// a definition is found or no parent scope remains.
|
|
func (s *Scope) BindReference(name string) (Definition, bool) {
|
|
if s != nil {
|
|
// first we check inside defs which include config variables, local variables and resource definitions
|
|
if def, ok := s.defs[name]; ok {
|
|
return def, true
|
|
}
|
|
|
|
// then we check the namespace of the outputs.
|
|
// it is unlikely that an output is being referenced by something but still possible
|
|
// that is why we check outputs after checking defs
|
|
if output, ok := s.outputs[name]; ok {
|
|
return output, true
|
|
}
|
|
|
|
if s.parent != nil {
|
|
return s.parent.BindReference(name)
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// BindFunctionReference returns the function definition that corresponds to the given name, if any. Each parent scope
|
|
// is checked until a definition is found or no parent scope remains.
|
|
func (s *Scope) BindFunctionReference(name string) (*Function, bool) {
|
|
if s != nil {
|
|
if fn, ok := s.functions[name]; ok {
|
|
return fn, true
|
|
}
|
|
if s.parent != nil {
|
|
return s.parent.BindFunctionReference(name)
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// isOutputDefinition returns whether the input definition is an output variable
|
|
func isOutputDefinition(def Definition) bool {
|
|
syntax := def.SyntaxNode()
|
|
if syntax != nil {
|
|
switch syntax := syntax.(type) {
|
|
case *hclsyntax.Block:
|
|
return syntax.Type == "output"
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Define maps the given name to the given definition. If the name is already defined in this scope, the existing
|
|
// definition is not overwritten and Define returns false.
|
|
func (s *Scope) Define(name string, def Definition) bool {
|
|
if s != nil {
|
|
if isOutputDefinition(def) {
|
|
// if the definition we have is an output
|
|
// then we only check inside the outputs namespace
|
|
if _, hasDef := s.outputs[name]; !hasDef {
|
|
s.outputs[name] = def
|
|
return true
|
|
}
|
|
} else {
|
|
if _, hasDef := s.defs[name]; !hasDef {
|
|
s.defs[name] = def
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// DefineFunction maps the given function name to the given function definition. If the function is alreadu defined in
|
|
// this scope, the definition is not overwritten and DefineFunction returns false.
|
|
func (s *Scope) DefineFunction(name string, def *Function) bool {
|
|
if s != nil {
|
|
if _, hasFunc := s.functions[name]; !hasFunc {
|
|
s.functions[name] = def
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// DefineScope defines a child scope with the given name. If the name is already defined in this scope, the existing
|
|
// definition is not overwritten and DefineScope returns false.
|
|
func (s *Scope) DefineScope(name string, syntax hclsyntax.Node) (*Scope, bool) {
|
|
if s != nil {
|
|
if _, exists := s.defs[name]; !exists {
|
|
child := &Scope{
|
|
parent: s,
|
|
syntax: syntax,
|
|
defs: map[string]Definition{},
|
|
outputs: map[string]Definition{},
|
|
functions: map[string]*Function{},
|
|
}
|
|
s.defs[name] = child
|
|
return child, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// Push defines an anonymous child scope associated with the given syntax node.
|
|
func (s *Scope) Push(syntax hclsyntax.Node) *Scope {
|
|
return &Scope{
|
|
parent: s,
|
|
syntax: syntax,
|
|
defs: map[string]Definition{},
|
|
outputs: map[string]Definition{},
|
|
functions: map[string]*Function{},
|
|
}
|
|
}
|
|
|
|
// Pop returns this scope's parent.
|
|
func (s *Scope) Pop() *Scope {
|
|
return s.parent
|
|
}
|
|
|
|
// Scopes is the interface that is used fetch the scope that should be used when binding a block or attribute.
|
|
type Scopes interface {
|
|
// GetScopesForBlock returns the Scopes that should be used when binding the given block.
|
|
GetScopesForBlock(block *hclsyntax.Block) (Scopes, hcl.Diagnostics)
|
|
// GetScopeForAttribute returns the *Scope that should be used when binding the given attribute.
|
|
GetScopeForAttribute(attribute *hclsyntax.Attribute) (*Scope, hcl.Diagnostics)
|
|
}
|
|
|
|
type staticScope struct {
|
|
scope *Scope
|
|
}
|
|
|
|
// GetScopesForBlock returns the scopes to use when binding the given block.
|
|
func (s staticScope) GetScopesForBlock(block *hclsyntax.Block) (Scopes, hcl.Diagnostics) {
|
|
return s, nil
|
|
}
|
|
|
|
// GetScopeForAttribute returns the scope to use when binding the given attribute.
|
|
func (s staticScope) GetScopeForAttribute(attribute *hclsyntax.Attribute) (*Scope, hcl.Diagnostics) {
|
|
return s.scope, nil
|
|
}
|
|
|
|
// StaticScope returns a Scopes that uses the given *Scope for all blocks and attributes.
|
|
func StaticScope(scope *Scope) Scopes {
|
|
return staticScope{scope: scope}
|
|
}
|