pulumi/pkg/codegen/hcl2/model/scope.go

273 lines
8.4 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
}
// Tracerse 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 two namespaces: one that is exclusive to functions and one that contains both variables and functions.
// When binding a reference, only the latter is checked; when binding a function, only the former is checked.
type Scope struct {
parent *Scope
syntax hclsyntax.Node
defs 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{},
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())}
}
memberName := name.AsString()
member, hasMember := s.BindReference(memberName)
if !hasMember {
return DynamicType, hcl.Diagnostics{undefinedVariable(memberName, traverser.SourceRange())}
}
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 {
if def, ok := s.defs[name]; ok {
return def, 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
}
// 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 _, 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{},
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{},
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}
}