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:
Pat Gavlin 2020-04-02 21:23:12 -07:00 committed by GitHub
parent e92b1b4e8d
commit c5782115d2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 1717 additions and 56 deletions

View file

@ -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.

View file

@ -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
}

View file

@ -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)

View file

@ -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.

View file

@ -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

View 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)
}
}

View file

@ -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
}

View 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))
}
}

View file

@ -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)