Various updates to the HCL2 semantic model. (#4281)
- Improve support for printing and triva - Add support for HCL2 type functions - Add visitors for body items
This commit is contained in:
parent
e92b1b4e8d
commit
c5782115d2
|
@ -15,6 +15,9 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/pulumi/pulumi/pkg/codegen/hcl2/syntax"
|
||||
|
@ -38,6 +41,25 @@ func (a *Attribute) SyntaxNode() hclsyntax.Node {
|
|||
return syntaxOrNone(a.Syntax)
|
||||
}
|
||||
|
||||
func (a *Attribute) hasLeadingTrivia() bool {
|
||||
return a.Tokens != nil
|
||||
}
|
||||
|
||||
func (a *Attribute) hasTrailingTrivia() bool {
|
||||
return a.Value.hasTrailingTrivia()
|
||||
}
|
||||
|
||||
func (a *Attribute) Format(f fmt.State, c rune) {
|
||||
a.print(f, &printer{})
|
||||
}
|
||||
|
||||
func (a *Attribute) print(w io.Writer, p *printer) {
|
||||
p.fprintf(w, "%v% v% v",
|
||||
a.Tokens.GetName().Or(hclsyntax.TokenIdent, a.Name),
|
||||
a.Tokens.GetEquals().Or(hclsyntax.TokenEqual),
|
||||
a.Value)
|
||||
}
|
||||
|
||||
func (*Attribute) isBodyItem() {}
|
||||
|
||||
// BindAttribute binds an HCL2 attribute using the given scope and token map.
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/pulumi/pulumi/pkg/codegen/hcl2/syntax"
|
||||
_syntax "github.com/pulumi/pulumi/pkg/codegen/hcl2/syntax"
|
||||
"github.com/pulumi/pulumi/sdk/go/common/util/contract"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
|
@ -28,21 +28,23 @@ import (
|
|||
type expressionBinder struct {
|
||||
anonSymbols map[*hclsyntax.AnonSymbolExpr]Definition
|
||||
scope *Scope
|
||||
tokens _syntax.TokenMap
|
||||
}
|
||||
|
||||
// BindExpression binds an HCL2 expression using the given scope and token map.
|
||||
func BindExpression(syntax hclsyntax.Node, scope *Scope, tokens syntax.TokenMap) (Expression, hcl.Diagnostics) {
|
||||
func BindExpression(syntax hclsyntax.Node, scope *Scope, tokens _syntax.TokenMap) (Expression, hcl.Diagnostics) {
|
||||
b := &expressionBinder{
|
||||
anonSymbols: map[*hclsyntax.AnonSymbolExpr]Definition{},
|
||||
scope: scope,
|
||||
tokens: tokens,
|
||||
}
|
||||
|
||||
return b.bindExpression(syntax)
|
||||
}
|
||||
|
||||
// BindExpressionText parses and binds an HCL2 expression using the given scope.
|
||||
func BindExpressionText(source string, scope *Scope) (Expression, hcl.Diagnostics) {
|
||||
syntax, tokens, diagnostics := syntax.ParseExpression(source, "<anonymous>", hcl.InitialPos)
|
||||
func BindExpressionText(source string, scope *Scope, initialPos hcl.Pos) (Expression, hcl.Diagnostics) {
|
||||
syntax, tokens, diagnostics := _syntax.ParseExpression(source, "<anonymous>", initialPos)
|
||||
if diagnostics.HasErrors() {
|
||||
return nil, diagnostics
|
||||
}
|
||||
|
@ -245,9 +247,12 @@ func (b *expressionBinder) bindBinaryOpExpression(syntax *hclsyntax.BinaryOpExpr
|
|||
typecheckDiags := typecheckArgs(syntax.Range(), signature, leftOperand, rightOperand)
|
||||
diagnostics = append(diagnostics, typecheckDiags...)
|
||||
|
||||
tokens, _ := b.tokens.ForNode(syntax).(*_syntax.BinaryOpTokens)
|
||||
return &BinaryOpExpression{
|
||||
Syntax: syntax,
|
||||
Tokens: tokens,
|
||||
LeftOperand: leftOperand,
|
||||
Operation: syntax.Op,
|
||||
RightOperand: rightOperand,
|
||||
exprType: liftOperationType(signature.ReturnType, leftOperand, rightOperand),
|
||||
}, diagnostics
|
||||
|
@ -279,6 +284,7 @@ func (b *expressionBinder) bindConditionalExpression(syntax *hclsyntax.Condition
|
|||
|
||||
return &ConditionalExpression{
|
||||
Syntax: syntax,
|
||||
Tokens: b.tokens.ForNode(syntax),
|
||||
Condition: condition,
|
||||
TrueResult: trueResult,
|
||||
FalseResult: falseResult,
|
||||
|
@ -361,11 +367,15 @@ func (b *expressionBinder) bindForExpression(syntax *hclsyntax.ForExpr) (Express
|
|||
// Push a scope for the key and value variables and define these vars.
|
||||
b.scope = b.scope.Push(syntax)
|
||||
defer func() { b.scope = b.scope.Pop() }()
|
||||
|
||||
var keyVariable *Variable
|
||||
if syntax.KeyVar != "" {
|
||||
ok := b.scope.Define(syntax.KeyVar, &Variable{Name: syntax.KeyVar, VariableType: keyType})
|
||||
keyVariable = &Variable{Name: syntax.KeyVar, VariableType: keyType}
|
||||
ok := b.scope.Define(syntax.KeyVar, keyVariable)
|
||||
contract.Assert(ok)
|
||||
}
|
||||
if ok := b.scope.Define(syntax.ValVar, &Variable{Name: syntax.ValVar, VariableType: valueType}); !ok {
|
||||
valueVariable := &Variable{Name: syntax.ValVar, VariableType: valueType}
|
||||
if ok := b.scope.Define(syntax.ValVar, valueVariable); !ok {
|
||||
diagnostics = append(diagnostics, nameAlreadyDefined(syntax.ValVar, syntax.Range()))
|
||||
}
|
||||
|
||||
|
@ -417,11 +427,16 @@ func (b *expressionBinder) bindForExpression(syntax *hclsyntax.ForExpr) (Express
|
|||
}
|
||||
|
||||
return &ForExpression{
|
||||
Syntax: syntax,
|
||||
Collection: collection,
|
||||
Key: key,
|
||||
Value: value,
|
||||
exprType: liftOperationType(resultType, liftArgs...),
|
||||
Syntax: syntax,
|
||||
Tokens: b.tokens.ForNode(syntax),
|
||||
KeyVariable: keyVariable,
|
||||
ValueVariable: valueVariable,
|
||||
Collection: collection,
|
||||
Key: key,
|
||||
Value: value,
|
||||
Condition: condition,
|
||||
Group: syntax.Group,
|
||||
exprType: liftOperationType(resultType, liftArgs...),
|
||||
}, diagnostics
|
||||
}
|
||||
|
||||
|
@ -434,6 +449,8 @@ func (b *expressionBinder) bindFunctionCallExpression(
|
|||
|
||||
var diagnostics hcl.Diagnostics
|
||||
|
||||
tokens, _ := b.tokens.ForNode(syntax).(*_syntax.FunctionCallTokens)
|
||||
|
||||
// Bind the function's arguments.
|
||||
args := make([]Expression, len(syntax.Args))
|
||||
for i, syntax := range syntax.Args {
|
||||
|
@ -448,6 +465,7 @@ func (b *expressionBinder) bindFunctionCallExpression(
|
|||
|
||||
return &FunctionCallExpression{
|
||||
Syntax: syntax,
|
||||
Tokens: tokens,
|
||||
Name: syntax.Name,
|
||||
Signature: StaticFunctionSignature{
|
||||
VarargsParameter: &Parameter{Name: "args", Type: DynamicType},
|
||||
|
@ -477,6 +495,7 @@ func (b *expressionBinder) bindFunctionCallExpression(
|
|||
|
||||
return &FunctionCallExpression{
|
||||
Syntax: syntax,
|
||||
Tokens: tokens,
|
||||
Name: syntax.Name,
|
||||
Signature: signature,
|
||||
Args: args,
|
||||
|
@ -508,8 +527,10 @@ func (b *expressionBinder) bindIndexExpression(syntax *hclsyntax.IndexExpr) (Exp
|
|||
|
||||
diagnostics = append(diagnostics, partDiags...)
|
||||
|
||||
tokens, _ := b.tokens.ForNode(syntax).(*_syntax.IndexTokens)
|
||||
return &IndexExpression{
|
||||
Syntax: syntax,
|
||||
Tokens: tokens,
|
||||
Collection: collection,
|
||||
Key: key,
|
||||
exprType: liftOperationType(part.(Type), collection, key),
|
||||
|
@ -533,8 +554,10 @@ func (b *expressionBinder) bindLiteralValueExpression(
|
|||
typ, diagnostics = DynamicType, hcl.Diagnostics{unsupportedLiteralValue(syntax)}
|
||||
}
|
||||
|
||||
tokens, _ := b.tokens.ForNode(syntax).(*_syntax.LiteralValueTokens)
|
||||
return &LiteralValueExpression{
|
||||
Syntax: syntax,
|
||||
Tokens: tokens,
|
||||
Value: v,
|
||||
exprType: typ,
|
||||
}, diagnostics
|
||||
|
@ -570,7 +593,12 @@ func (b *expressionBinder) bindObjectConsExpression(syntax *hclsyntax.ObjectCons
|
|||
for _, item := range items {
|
||||
types = append(types, item.Value.Type())
|
||||
|
||||
keyLit, ok := item.Key.(*LiteralValueExpression)
|
||||
key := item.Key
|
||||
if template, ok := key.(*TemplateExpression); ok && len(template.Parts) == 1 {
|
||||
key = template.Parts[0]
|
||||
}
|
||||
|
||||
keyLit, ok := key.(*LiteralValueExpression)
|
||||
if ok {
|
||||
key, err := convert.Convert(keyLit.Value, cty.String)
|
||||
if err == nil {
|
||||
|
@ -588,8 +616,10 @@ func (b *expressionBinder) bindObjectConsExpression(syntax *hclsyntax.ObjectCons
|
|||
typ = NewObjectType(properties)
|
||||
}
|
||||
|
||||
tokens, _ := b.tokens.ForNode(syntax).(*_syntax.ObjectConsTokens)
|
||||
return &ObjectConsExpression{
|
||||
Syntax: syntax,
|
||||
Tokens: tokens,
|
||||
Items: items,
|
||||
exprType: liftOperationType(typ, keys...),
|
||||
}, diagnostics
|
||||
|
@ -599,10 +629,13 @@ func (b *expressionBinder) bindObjectConsExpression(syntax *hclsyntax.ObjectCons
|
|||
func (b *expressionBinder) bindObjectConsKeyExpr(syntax *hclsyntax.ObjectConsKeyExpr) (Expression, hcl.Diagnostics) {
|
||||
if !syntax.ForceNonLiteral {
|
||||
if name := hcl.ExprAsKeyword(syntax); name != "" {
|
||||
return b.bindExpression(&hclsyntax.LiteralValueExpr{
|
||||
expr, diags := b.bindExpression(&hclsyntax.LiteralValueExpr{
|
||||
Val: cty.StringVal(name),
|
||||
SrcRange: syntax.Range(),
|
||||
})
|
||||
lit := expr.(*LiteralValueExpression)
|
||||
lit.Tokens, _ = b.tokens.ForNode(syntax).(*_syntax.LiteralValueTokens)
|
||||
return lit, diags
|
||||
}
|
||||
}
|
||||
return b.bindExpression(syntax.Wrapped)
|
||||
|
@ -618,10 +651,13 @@ func (b *expressionBinder) bindRelativeTraversalExpression(
|
|||
parts, partDiags := b.bindTraversalParts(source.Type(), syntax.Traversal)
|
||||
diagnostics = append(diagnostics, partDiags...)
|
||||
|
||||
tokens, _ := b.tokens.ForNode(syntax).(*_syntax.RelativeTraversalTokens)
|
||||
return &RelativeTraversalExpression{
|
||||
Syntax: syntax,
|
||||
Source: source,
|
||||
Parts: parts,
|
||||
Syntax: syntax,
|
||||
Tokens: tokens,
|
||||
Source: source,
|
||||
Parts: parts,
|
||||
Traversal: syntax.Traversal,
|
||||
}, diagnostics
|
||||
}
|
||||
|
||||
|
@ -631,6 +667,8 @@ func (b *expressionBinder) bindRelativeTraversalExpression(
|
|||
func (b *expressionBinder) bindScopeTraversalExpression(
|
||||
syntax *hclsyntax.ScopeTraversalExpr) (Expression, hcl.Diagnostics) {
|
||||
|
||||
tokens, _ := b.tokens.ForNode(syntax).(*_syntax.ScopeTraversalTokens)
|
||||
|
||||
def, ok := b.scope.BindReference(syntax.Traversal.RootName())
|
||||
if !ok {
|
||||
parts := make([]Traversable, len(syntax.Traversal))
|
||||
|
@ -638,15 +676,21 @@ func (b *expressionBinder) bindScopeTraversalExpression(
|
|||
parts[i] = DynamicType
|
||||
}
|
||||
return &ScopeTraversalExpression{
|
||||
Syntax: syntax,
|
||||
Parts: parts,
|
||||
Syntax: syntax,
|
||||
Tokens: tokens,
|
||||
Parts: parts,
|
||||
RootName: syntax.Traversal.RootName(),
|
||||
Traversal: syntax.Traversal,
|
||||
}, hcl.Diagnostics{undefinedVariable(syntax.Traversal.SimpleSplit().Abs.SourceRange())}
|
||||
}
|
||||
|
||||
parts, diagnostics := b.bindTraversalParts(def, syntax.Traversal.SimpleSplit().Rel)
|
||||
return &ScopeTraversalExpression{
|
||||
Syntax: syntax,
|
||||
Parts: parts,
|
||||
Syntax: syntax,
|
||||
Tokens: tokens,
|
||||
Parts: parts,
|
||||
RootName: syntax.Traversal.RootName(),
|
||||
Traversal: syntax.Traversal,
|
||||
}, diagnostics
|
||||
}
|
||||
|
||||
|
@ -686,8 +730,10 @@ func (b *expressionBinder) bindSplatExpression(syntax *hclsyntax.SplatExpr) (Exp
|
|||
each, eachDiags := b.bindExpression(syntax.Each)
|
||||
diagnostics = append(diagnostics, eachDiags...)
|
||||
|
||||
tokens, _ := b.tokens.ForNode(syntax).(*_syntax.SplatTokens)
|
||||
return &SplatExpression{
|
||||
Syntax: syntax,
|
||||
Tokens: tokens,
|
||||
Source: source,
|
||||
Each: each,
|
||||
Item: item,
|
||||
|
@ -698,10 +744,6 @@ func (b *expressionBinder) bindSplatExpression(syntax *hclsyntax.SplatExpr) (Exp
|
|||
// bindTemplateExpression binds a template expression. The result is always a string. If any of the parts of the
|
||||
// expression are eventual, the result is eventual.
|
||||
func (b *expressionBinder) bindTemplateExpression(syntax *hclsyntax.TemplateExpr) (Expression, hcl.Diagnostics) {
|
||||
if syntax.IsStringLiteral() {
|
||||
return b.bindExpression(syntax.Parts[0])
|
||||
}
|
||||
|
||||
var diagnostics hcl.Diagnostics
|
||||
parts := make([]Expression, len(syntax.Parts))
|
||||
for i, syntax := range syntax.Parts {
|
||||
|
@ -709,8 +751,10 @@ func (b *expressionBinder) bindTemplateExpression(syntax *hclsyntax.TemplateExpr
|
|||
parts[i], diagnostics = part, append(diagnostics, partDiags...)
|
||||
}
|
||||
|
||||
tokens, _ := b.tokens.ForNode(syntax).(*_syntax.TemplateTokens)
|
||||
return &TemplateExpression{
|
||||
Syntax: syntax,
|
||||
Tokens: tokens,
|
||||
Parts: parts,
|
||||
exprType: liftOperationType(StringType, parts...),
|
||||
}, diagnostics
|
||||
|
@ -751,8 +795,10 @@ func (b *expressionBinder) bindTupleConsExpression(syntax *hclsyntax.TupleConsEx
|
|||
elementTypes[i] = expr.Type()
|
||||
}
|
||||
|
||||
tokens, _ := b.tokens.ForNode(syntax).(*_syntax.TupleConsTokens)
|
||||
return &TupleConsExpression{
|
||||
Syntax: syntax,
|
||||
Tokens: tokens,
|
||||
Expressions: exprs,
|
||||
exprType: NewTupleType(elementTypes...),
|
||||
}, diagnostics
|
||||
|
@ -774,9 +820,12 @@ func (b *expressionBinder) bindUnaryOpExpression(syntax *hclsyntax.UnaryOpExpr)
|
|||
typecheckDiags := typecheckArgs(syntax.Range(), signature, operand)
|
||||
diagnostics = append(diagnostics, typecheckDiags...)
|
||||
|
||||
tokens, _ := b.tokens.ForNode(syntax).(*_syntax.UnaryOpTokens)
|
||||
return &UnaryOpExpression{
|
||||
Syntax: syntax,
|
||||
Operand: operand,
|
||||
exprType: liftOperationType(signature.ReturnType, operand),
|
||||
Syntax: syntax,
|
||||
Tokens: tokens,
|
||||
Operation: syntax.Op,
|
||||
Operand: operand,
|
||||
exprType: liftOperationType(signature.ReturnType, operand),
|
||||
}, diagnostics
|
||||
}
|
||||
|
|
|
@ -17,44 +17,48 @@ package model
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/pulumi/pulumi/pkg/codegen/hcl2/syntax"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestBindLiteral(t *testing.T) {
|
||||
expr, diags := BindExpressionText("false", nil)
|
||||
expr, diags := BindExpressionText("false", nil, hcl.Pos{})
|
||||
assert.Len(t, diags, 0)
|
||||
assert.Equal(t, BoolType, expr.Type())
|
||||
lit, ok := expr.(*LiteralValueExpression)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, cty.False, lit.Value)
|
||||
|
||||
expr, diags = BindExpressionText("true", nil)
|
||||
expr, diags = BindExpressionText("true", nil, hcl.Pos{})
|
||||
assert.Len(t, diags, 0)
|
||||
assert.Equal(t, BoolType, expr.Type())
|
||||
lit, ok = expr.(*LiteralValueExpression)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, cty.True, lit.Value)
|
||||
|
||||
expr, diags = BindExpressionText("0", nil)
|
||||
expr, diags = BindExpressionText("0", nil, hcl.Pos{})
|
||||
assert.Len(t, diags, 0)
|
||||
assert.Equal(t, NumberType, expr.Type())
|
||||
lit, ok = expr.(*LiteralValueExpression)
|
||||
assert.True(t, ok)
|
||||
assert.True(t, cty.NumberIntVal(0).RawEquals(lit.Value))
|
||||
|
||||
expr, diags = BindExpressionText("3.14", nil)
|
||||
expr, diags = BindExpressionText("3.14", nil, hcl.Pos{})
|
||||
assert.Len(t, diags, 0)
|
||||
assert.Equal(t, NumberType, expr.Type())
|
||||
lit, ok = expr.(*LiteralValueExpression)
|
||||
assert.True(t, ok)
|
||||
assert.True(t, cty.MustParseNumberVal("3.14").RawEquals(lit.Value))
|
||||
|
||||
expr, diags = BindExpressionText(`"foo"`, nil)
|
||||
expr, diags = BindExpressionText(`"foo"`, nil, hcl.Pos{})
|
||||
assert.Len(t, diags, 0)
|
||||
assert.Equal(t, StringType, expr.Type())
|
||||
lit, ok = expr.(*LiteralValueExpression)
|
||||
template, ok := expr.(*TemplateExpression)
|
||||
assert.True(t, ok)
|
||||
assert.Len(t, template.Parts, 1)
|
||||
lit, ok = template.Parts[0].(*LiteralValueExpression)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, cty.StringVal("foo"), lit.Value)
|
||||
}
|
||||
|
@ -119,7 +123,7 @@ func TestBindBinaryOp(t *testing.T) {
|
|||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.x, func(t *testing.T) {
|
||||
expr, diags := BindExpressionText(c.x, scope)
|
||||
expr, diags := BindExpressionText(c.x, scope, hcl.Pos{})
|
||||
assert.Len(t, diags, 0)
|
||||
assert.Equal(t, c.t, expr.Type())
|
||||
_, ok := expr.(*BinaryOpExpression)
|
||||
|
@ -150,7 +154,7 @@ func TestBindConditional(t *testing.T) {
|
|||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.x, func(t *testing.T) {
|
||||
expr, diags := BindExpressionText(c.x, scope)
|
||||
expr, diags := BindExpressionText(c.x, scope, hcl.Pos{})
|
||||
assert.Len(t, diags, 0)
|
||||
assert.Equal(t, c.t, expr.Type())
|
||||
_, ok := expr.(*ConditionalExpression)
|
||||
|
@ -210,7 +214,7 @@ func TestBindFor(t *testing.T) {
|
|||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.x, func(t *testing.T) {
|
||||
expr, diags := BindExpressionText(c.x, scope)
|
||||
expr, diags := BindExpressionText(c.x, scope, hcl.Pos{})
|
||||
assert.Len(t, diags, 0)
|
||||
assert.Equal(t, c.t, expr.Type())
|
||||
_, ok := expr.(*ForExpression)
|
||||
|
@ -265,7 +269,7 @@ func TestBindFunctionCall(t *testing.T) {
|
|||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.x, func(t *testing.T) {
|
||||
expr, diags := BindExpressionText(c.x, scope)
|
||||
expr, diags := BindExpressionText(c.x, scope, hcl.Pos{})
|
||||
assert.Len(t, diags, 0)
|
||||
assert.Equal(t, c.t, expr.Type())
|
||||
_, ok := expr.(*FunctionCallExpression)
|
||||
|
@ -331,7 +335,7 @@ func TestBindIndex(t *testing.T) {
|
|||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.x, func(t *testing.T) {
|
||||
expr, diags := BindExpressionText(c.x, scope)
|
||||
expr, diags := BindExpressionText(c.x, scope, hcl.Pos{})
|
||||
assert.Len(t, diags, 0)
|
||||
assert.Equal(t, c.t, expr.Type())
|
||||
_, ok := expr.(*IndexExpression)
|
||||
|
@ -371,7 +375,7 @@ func TestBindObjectCons(t *testing.T) {
|
|||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.x, func(t *testing.T) {
|
||||
expr, diags := BindExpressionText(c.x, scope)
|
||||
expr, diags := BindExpressionText(c.x, scope, hcl.Pos{})
|
||||
assert.Len(t, diags, 0)
|
||||
assert.Equal(t, c.t, expr.Type())
|
||||
_, ok := expr.(*ObjectConsExpression)
|
||||
|
@ -423,7 +427,7 @@ func TestBindRelativeTraversal(t *testing.T) {
|
|||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.x, func(t *testing.T) {
|
||||
expr, diags := BindExpressionText(c.x, scope)
|
||||
expr, diags := BindExpressionText(c.x, scope, hcl.Pos{})
|
||||
assert.Len(t, diags, 0)
|
||||
assert.Equal(t, c.t, expr.Type())
|
||||
_, ok := expr.(*RelativeTraversalExpression)
|
||||
|
@ -492,7 +496,7 @@ func TestBindScopeTraversal(t *testing.T) {
|
|||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.x, func(t *testing.T) {
|
||||
expr, diags := BindExpressionText(c.x, scope)
|
||||
expr, diags := BindExpressionText(c.x, scope, hcl.Pos{})
|
||||
assert.Len(t, diags, 0)
|
||||
assert.Equal(t, c.t, expr.Type())
|
||||
_, ok := expr.(*ScopeTraversalExpression)
|
||||
|
@ -555,7 +559,7 @@ func TestBindSplat(t *testing.T) {
|
|||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.x, func(t *testing.T) {
|
||||
expr, diags := BindExpressionText(c.x, scope)
|
||||
expr, diags := BindExpressionText(c.x, scope, hcl.Pos{})
|
||||
assert.Len(t, diags, 0)
|
||||
assert.Equal(t, c.t, expr.Type())
|
||||
_, ok := expr.(*SplatExpression)
|
||||
|
@ -583,8 +587,6 @@ func TestBindTemplate(t *testing.T) {
|
|||
|
||||
cases := []exprTestCase{
|
||||
// Unwrapped interpolations
|
||||
{x: `"foo"`, t: StringType, xt: &LiteralValueExpression{}},
|
||||
{x: `"${"foo"}"`, t: StringType, xt: &LiteralValueExpression{}},
|
||||
{x: `"${0}"`, t: NumberType, xt: &LiteralValueExpression{}},
|
||||
{x: `"${true}"`, t: BoolType, xt: &LiteralValueExpression{}},
|
||||
{x: `"${d}"`, t: NewListType(StringType), xt: &ScopeTraversalExpression{}},
|
||||
|
@ -617,7 +619,7 @@ func TestBindTemplate(t *testing.T) {
|
|||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.x, func(t *testing.T) {
|
||||
expr, diags := BindExpressionText(c.x, scope)
|
||||
expr, diags := BindExpressionText(c.x, scope, hcl.Pos{})
|
||||
assert.Len(t, diags, 0)
|
||||
assert.Equal(t, c.t, expr.Type())
|
||||
|
||||
|
@ -651,7 +653,7 @@ func TestBindTupleCons(t *testing.T) {
|
|||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.x, func(t *testing.T) {
|
||||
expr, diags := BindExpressionText(c.x, scope)
|
||||
expr, diags := BindExpressionText(c.x, scope, hcl.Pos{})
|
||||
assert.Len(t, diags, 0)
|
||||
assert.Equal(t, c.t, expr.Type())
|
||||
_, ok := expr.(*TupleConsExpression)
|
||||
|
@ -684,7 +686,7 @@ func TestBindUnaryOp(t *testing.T) {
|
|||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.x, func(t *testing.T) {
|
||||
expr, diags := BindExpressionText(c.x, scope)
|
||||
expr, diags := BindExpressionText(c.x, scope, hcl.Pos{})
|
||||
assert.Len(t, diags, 0)
|
||||
assert.Equal(t, c.t, expr.Type())
|
||||
_, ok := expr.(*UnaryOpExpression)
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/pulumi/pulumi/pkg/codegen/hcl2/syntax"
|
||||
|
@ -41,6 +44,60 @@ func (b *Block) SyntaxNode() hclsyntax.Node {
|
|||
return syntaxOrNone(b.Syntax)
|
||||
}
|
||||
|
||||
func (b *Block) hasLeadingTrivia() bool {
|
||||
return b.Tokens != nil
|
||||
}
|
||||
|
||||
func (b *Block) hasTrailingTrivia() bool {
|
||||
return b.Tokens != nil
|
||||
}
|
||||
|
||||
func (b *Block) Format(f fmt.State, c rune) {
|
||||
b.print(f, &printer{})
|
||||
}
|
||||
|
||||
func (b *Block) print(w io.Writer, p *printer) {
|
||||
// Print the type.
|
||||
p.fprintf(w, "%v", b.Tokens.GetType().Or(hclsyntax.TokenIdent, b.Type))
|
||||
|
||||
// Print the labels with leading and trailing trivia.
|
||||
labelTokens := b.Tokens.GetLabels()
|
||||
for i, l := range b.Labels {
|
||||
var t syntax.Token
|
||||
if i < len(labelTokens) {
|
||||
t = labelTokens[i]
|
||||
}
|
||||
if hclsyntax.ValidIdentifier(l) {
|
||||
t = t.Or(hclsyntax.TokenIdent, l)
|
||||
} else {
|
||||
t = t.Or(hclsyntax.TokenQuotedLit, fmt.Sprintf("%q", l))
|
||||
}
|
||||
p.fprintf(w, "% v", t)
|
||||
}
|
||||
if len(b.Labels) < len(b.Tokens.Labels) {
|
||||
for _, l := range b.Tokens.Labels[len(b.Labels):] {
|
||||
p.fprintf(w, "%v", syntax.Token{
|
||||
LeadingTrivia: l.LeadingTrivia,
|
||||
TrailingTrivia: l.TrailingTrivia,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Print the opening brace.
|
||||
p.fprintf(w, "% v", b.Tokens.GetOpenBrace().Or(hclsyntax.TokenOBrace))
|
||||
|
||||
// Print the block contents.
|
||||
p.indented(func() {
|
||||
b.Body.print(w, p)
|
||||
})
|
||||
|
||||
if b.Tokens != nil {
|
||||
p.fprintf(w, "%v", b.Tokens.GetCloseBrace().Or(hclsyntax.TokenCBrace))
|
||||
} else {
|
||||
p.fprintf(w, "\n%s}", p.indent)
|
||||
}
|
||||
}
|
||||
|
||||
func (*Block) isBodyItem() {}
|
||||
|
||||
// BindBlock binds an HCL2 block using the given scopes and token map.
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/pulumi/pulumi/pkg/codegen/hcl2/syntax"
|
||||
|
@ -23,6 +26,8 @@ import (
|
|||
|
||||
// BodyItem represents either an *Attribute or a *Block that is part of an HCL2 Body.
|
||||
type BodyItem interface {
|
||||
printable
|
||||
|
||||
// SyntaxNode returns syntax node of the item.
|
||||
SyntaxNode() hclsyntax.Node
|
||||
|
||||
|
@ -45,6 +50,54 @@ func (b *Body) SyntaxNode() hclsyntax.Node {
|
|||
return syntaxOrNone(b.Syntax)
|
||||
}
|
||||
|
||||
func (b *Body) hasLeadingTrivia() bool {
|
||||
return len(b.Items) > 0 && b.Items[0].hasLeadingTrivia()
|
||||
}
|
||||
|
||||
func (b *Body) hasTrailingTrivia() bool {
|
||||
if eof := b.Tokens.GetEndOfFile(); eof != nil {
|
||||
return true
|
||||
}
|
||||
return len(b.Items) > 0 && b.Items[len(b.Items)-1].hasTrailingTrivia()
|
||||
}
|
||||
|
||||
func (b *Body) Format(f fmt.State, c rune) {
|
||||
b.print(f, &printer{})
|
||||
}
|
||||
|
||||
func (b *Body) print(w io.Writer, p *printer) {
|
||||
// Print the items, separated by newlines.
|
||||
for _, item := range b.Items {
|
||||
p.fprintf(w, "% v", item)
|
||||
}
|
||||
|
||||
// If the body has an end-of-file token, print it.
|
||||
if b.Tokens.GetEndOfFile() != nil {
|
||||
p.fprintf(w, "%v", b.Tokens.EndOfFile)
|
||||
}
|
||||
}
|
||||
|
||||
// Attribute returns the attribute with the givne in the body if any exists.
|
||||
func (b *Body) Attribute(name string) (*Attribute, bool) {
|
||||
for _, item := range b.Items {
|
||||
if attr, ok := item.(*Attribute); ok && attr.Name == name {
|
||||
return attr, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Blocks returns all blocks in the body with the given type.
|
||||
func (b *Body) Blocks(typ string) []*Block {
|
||||
var blocks []*Block
|
||||
for _, item := range b.Items {
|
||||
if block, ok := item.(*Block); ok && block.Type == typ {
|
||||
blocks = append(blocks, block)
|
||||
}
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
// BindBody binds an HCL2 body using the given scopes and token map.
|
||||
func BindBody(body *hclsyntax.Body, scopes Scopes, tokens syntax.TokenMap) (*Body, hcl.Diagnostics) {
|
||||
var diagnostics hcl.Diagnostics
|
||||
|
|
File diff suppressed because it is too large
Load diff
68
pkg/codegen/hcl2/model/printer.go
Normal file
68
pkg/codegen/hcl2/model/printer.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
// 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 (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type printable interface {
|
||||
print(w io.Writer, p *printer)
|
||||
hasLeadingTrivia() bool
|
||||
hasTrailingTrivia() bool
|
||||
}
|
||||
|
||||
type printer struct {
|
||||
indent string
|
||||
}
|
||||
|
||||
type formatter func(f fmt.State, c rune)
|
||||
|
||||
func (fn formatter) Format(f fmt.State, c rune) {
|
||||
fn(f, c)
|
||||
}
|
||||
|
||||
func (p *printer) indented(f func()) {
|
||||
p.indent += " "
|
||||
f()
|
||||
p.indent = p.indent[:len(p.indent)-4]
|
||||
}
|
||||
|
||||
func (p *printer) format(f fmt.State, c rune, pp printable) {
|
||||
if f.Flag(' ') && !pp.hasLeadingTrivia() {
|
||||
switch pp.(type) {
|
||||
case BodyItem:
|
||||
p.fprintf(f, "\n%s", p.indent)
|
||||
case Expression:
|
||||
p.fprintf(f, " ")
|
||||
}
|
||||
}
|
||||
pp.print(f, p)
|
||||
}
|
||||
|
||||
func (p *printer) fprintf(w io.Writer, f string, v ...interface{}) {
|
||||
for i, e := range v {
|
||||
if printable, ok := e.(printable); ok {
|
||||
v[i] = formatter(func(f fmt.State, c rune) {
|
||||
p.format(f, c, printable)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintf(w, f, v...); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -135,7 +135,22 @@ func (t *OpaqueType) ConversionFrom(src Type) ConversionKind {
|
|||
|
||||
func (t *OpaqueType) String() string {
|
||||
if t.s == "" {
|
||||
t.s = fmt.Sprintf("opaque(%s)", t.Name)
|
||||
switch t {
|
||||
case NumberType:
|
||||
t.s = "number"
|
||||
case IntType:
|
||||
t.s = "int"
|
||||
case BoolType:
|
||||
t.s = "bool"
|
||||
case StringType:
|
||||
t.s = "string"
|
||||
default:
|
||||
if hclsyntax.ValidIdentifier(t.Name) {
|
||||
t.s = t.Name
|
||||
} else {
|
||||
t.s = fmt.Sprintf("type(%s)", t.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return t.s
|
||||
}
|
||||
|
|
101
pkg/codegen/hcl2/model/type_scope.go
Normal file
101
pkg/codegen/hcl2/model/type_scope.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/pulumi/pulumi/pkg/codegen/hcl2/syntax"
|
||||
)
|
||||
|
||||
var typeBuiltins = map[string]Type{
|
||||
"string": StringType,
|
||||
"number": NumberType,
|
||||
"int": IntType,
|
||||
"bool": BoolType,
|
||||
}
|
||||
|
||||
var typeFunctions = map[string]FunctionSignature{
|
||||
"list": GenericFunctionSignature(func(args []Expression) (StaticFunctionSignature, hcl.Diagnostics) {
|
||||
resultType := Type(DynamicType)
|
||||
if len(args) == 1 {
|
||||
resultType = NewListType(args[0].Type())
|
||||
}
|
||||
return StaticFunctionSignature{
|
||||
Parameters: []Parameter{{Name: "elementType", Type: DynamicType}},
|
||||
ReturnType: resultType,
|
||||
}, nil
|
||||
}),
|
||||
"set": GenericFunctionSignature(func(args []Expression) (StaticFunctionSignature, hcl.Diagnostics) {
|
||||
resultType := Type(DynamicType)
|
||||
if len(args) == 1 {
|
||||
resultType = NewSetType(args[0].Type())
|
||||
}
|
||||
return StaticFunctionSignature{
|
||||
Parameters: []Parameter{{Name: "elementType", Type: DynamicType}},
|
||||
ReturnType: resultType,
|
||||
}, nil
|
||||
}),
|
||||
"map": GenericFunctionSignature(func(args []Expression) (StaticFunctionSignature, hcl.Diagnostics) {
|
||||
resultType := Type(DynamicType)
|
||||
if len(args) == 1 {
|
||||
resultType = NewMapType(args[0].Type())
|
||||
}
|
||||
return StaticFunctionSignature{
|
||||
Parameters: []Parameter{{Name: "elementType", Type: DynamicType}},
|
||||
ReturnType: resultType,
|
||||
}, nil
|
||||
}),
|
||||
"object": GenericFunctionSignature(func(args []Expression) (StaticFunctionSignature, hcl.Diagnostics) {
|
||||
var diagnostics hcl.Diagnostics
|
||||
resultType := Type(DynamicType)
|
||||
if len(args) == 1 {
|
||||
if _, isObjectType := args[0].Type().(*ObjectType); isObjectType {
|
||||
resultType = args[0].Type()
|
||||
} else {
|
||||
rng := args[0].SyntaxNode().Range()
|
||||
diagnostics = hcl.Diagnostics{{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "the argument to object() must be an object type",
|
||||
Subject: &rng,
|
||||
}}
|
||||
}
|
||||
}
|
||||
return StaticFunctionSignature{
|
||||
Parameters: []Parameter{{Name: "objectType", Type: DynamicType}},
|
||||
ReturnType: resultType,
|
||||
}, diagnostics
|
||||
}),
|
||||
"tuple": GenericFunctionSignature(func(args []Expression) (StaticFunctionSignature, hcl.Diagnostics) {
|
||||
var diagnostics hcl.Diagnostics
|
||||
resultType := Type(DynamicType)
|
||||
if len(args) == 1 {
|
||||
if _, isTupleType := args[0].Type().(*TupleType); isTupleType {
|
||||
resultType = args[0].Type()
|
||||
} else {
|
||||
rng := args[0].SyntaxNode().Range()
|
||||
diagnostics = hcl.Diagnostics{{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "the argument to tuple() must be an tuple type",
|
||||
Subject: &rng,
|
||||
}}
|
||||
}
|
||||
}
|
||||
return StaticFunctionSignature{
|
||||
Parameters: []Parameter{{Name: "tupleType", Type: DynamicType}},
|
||||
ReturnType: resultType,
|
||||
}, diagnostics
|
||||
}),
|
||||
}
|
||||
|
||||
var TypeScope *Scope
|
||||
|
||||
func init() {
|
||||
TypeScope = NewRootScope(syntax.None)
|
||||
for name, typ := range typeBuiltins {
|
||||
TypeScope.Define(name, &Variable{
|
||||
Name: name,
|
||||
VariableType: typ,
|
||||
})
|
||||
}
|
||||
for name, sig := range typeFunctions {
|
||||
TypeScope.DefineFunction(name, NewFunction(sig))
|
||||
}
|
||||
}
|
|
@ -19,6 +19,52 @@ import (
|
|||
"github.com/pulumi/pulumi/sdk/go/common/util/contract"
|
||||
)
|
||||
|
||||
// A BodyItemVisitor is a function that visits and optionally replaces the contents of a body item.
|
||||
type BodyItemVisitor func(n BodyItem) (BodyItem, hcl.Diagnostics)
|
||||
|
||||
func IdentityBodyItemVisitor(n BodyItem) (BodyItem, hcl.Diagnostics) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func visitBlock(n *Block, pre, post BodyItemVisitor) (BodyItem, hcl.Diagnostics) {
|
||||
var diagnostics hcl.Diagnostics
|
||||
|
||||
var items []BodyItem
|
||||
for _, item := range n.Body.Items {
|
||||
newItem, diags := VisitBodyItem(item, pre, post)
|
||||
diagnostics = append(diagnostics, diags...)
|
||||
|
||||
if newItem != nil {
|
||||
items = append(items, newItem)
|
||||
}
|
||||
}
|
||||
n.Body.Items = items
|
||||
|
||||
block, diags := post(n)
|
||||
return block, append(diagnostics, diags...)
|
||||
}
|
||||
|
||||
func VisitBodyItem(n BodyItem, pre, post BodyItemVisitor) (BodyItem, hcl.Diagnostics) {
|
||||
if n == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
nn, preDiags := pre(n)
|
||||
|
||||
var postDiags hcl.Diagnostics
|
||||
switch n := nn.(type) {
|
||||
case *Attribute:
|
||||
nn, postDiags = post(n)
|
||||
case *Block:
|
||||
nn, postDiags = visitBlock(n, pre, post)
|
||||
default:
|
||||
contract.Failf("unexpected node type in visitExpression: %T", n)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nn, append(preDiags, postDiags...)
|
||||
}
|
||||
|
||||
// An ExpressionVisitor is a function that visits and optionally replaces a node in an expression tree.
|
||||
type ExpressionVisitor func(n Expression) (Expression, hcl.Diagnostics)
|
||||
|
||||
|
|
Loading…
Reference in a new issue