112 lines
3.2 KiB
Go
112 lines
3.2 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 (
|
|
"strings"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
// Traversable represents an entity that can be traversed by an HCL2 traverser.
|
|
type Traversable interface {
|
|
// Traverse attempts to traverse the receiver using the given traverser.
|
|
Traverse(t hcl.Traverser) (Traversable, hcl.Diagnostics)
|
|
}
|
|
|
|
// TypedTraversable is a Traversable that has an associated type.
|
|
type TypedTraversable interface {
|
|
Traversable
|
|
|
|
Type() Type
|
|
}
|
|
|
|
// ValueTraversable is a Traversable that has an associated value.
|
|
type ValueTraversable interface {
|
|
Traversable
|
|
|
|
Value(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
|
|
}
|
|
|
|
// GetTraversableType returns the type of the given Traversable:
|
|
// - If the Traversable is a TypedTraversable, this returns t.Type()
|
|
// - If the Traversable is a Type, this returns t
|
|
// - Otherwise, this returns DynamicType
|
|
func GetTraversableType(t Traversable) Type {
|
|
switch t := t.(type) {
|
|
case TypedTraversable:
|
|
return t.Type()
|
|
case Type:
|
|
return t
|
|
default:
|
|
return DynamicType
|
|
}
|
|
}
|
|
|
|
// GetTraverserKey extracts the value and type of the key associated with the given traverser.
|
|
func GetTraverserKey(t hcl.Traverser) (cty.Value, Type) {
|
|
switch t := t.(type) {
|
|
case hcl.TraverseAttr:
|
|
return cty.StringVal(t.Name), StringType
|
|
case hcl.TraverseIndex:
|
|
if t.Key.Type().Equals(typeCapsule) {
|
|
return cty.DynamicVal, *(t.Key.EncapsulatedValue().(*Type))
|
|
}
|
|
return t.Key, ctyTypeToType(t.Key.Type(), false)
|
|
default:
|
|
contract.Failf("unexpected traverser of type %T (%v)", t, t.SourceRange())
|
|
return cty.DynamicVal, DynamicType
|
|
}
|
|
}
|
|
|
|
// bindTraversalParts computes the type for each element of the given traversal.
|
|
func bindTraversalParts(receiver Traversable, traversal hcl.Traversal,
|
|
allowMissingVariables bool) ([]Traversable, hcl.Diagnostics) {
|
|
|
|
parts := make([]Traversable, len(traversal)+1)
|
|
parts[0] = receiver
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
for i, part := range traversal {
|
|
nextReceiver, partDiags := parts[i].Traverse(part)
|
|
|
|
// TODO(pdg): proper options for Traverse
|
|
if allowMissingVariables {
|
|
var diags hcl.Diagnostics
|
|
for _, d := range partDiags {
|
|
if !strings.HasPrefix(d.Summary, "undefined variable") {
|
|
diags = append(diags, d)
|
|
}
|
|
}
|
|
partDiags = diags
|
|
}
|
|
|
|
parts[i+1], diagnostics = nextReceiver, append(diagnostics, partDiags...)
|
|
}
|
|
|
|
switch parts[len(parts)-1].(type) {
|
|
case TypedTraversable, Type:
|
|
// OK
|
|
default:
|
|
// TODO(pdg): improve this diagnostic
|
|
if !allowMissingVariables {
|
|
diagnostics = append(diagnostics, undefinedVariable("", traversal.SourceRange()))
|
|
}
|
|
}
|
|
|
|
return parts, diagnostics
|
|
}
|