// 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"
	"math/big"
	"strconv"

	"github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/hcl/v2/hclsyntax"
	"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax"
	"github.com/pulumi/pulumi/sdk/v3/go/common/slice"
	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
	"github.com/zclconf/go-cty/cty"
	"github.com/zclconf/go-cty/cty/convert"
)

// Expression represents a semantically-analyzed HCL2 expression.
type Expression interface {
	printable

	// SyntaxNode returns the hclsyntax.Node associated with the expression.
	SyntaxNode() hclsyntax.Node
	// NodeTokens returns the syntax.Tokens associated with the expression.
	NodeTokens() syntax.NodeTokens

	// SetLeadingTrivia sets the leading trivia associated with the expression.
	SetLeadingTrivia(syntax.TriviaList)
	// SetTrailingTrivia sets the trailing trivia associated with the expression.
	SetTrailingTrivia(syntax.TriviaList)

	// Type returns the type of the expression.
	Type() Type
	// Typecheck recomputes the type of the expression, optionally typechecking its operands first.
	Typecheck(typecheckOperands bool) hcl.Diagnostics

	// Evaluate evaluates the expression.
	Evaluate(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics)

	isExpression()
}

func identToken(token syntax.Token, ident string) syntax.Token {
	if string(token.Raw.Bytes) != ident {
		token.Raw.Bytes = []byte(ident)
	}
	return token
}

func exprHasLeadingTrivia(parens syntax.Parentheses, first interface{}) bool {
	if parens.Any() {
		return true
	}
	switch first := first.(type) {
	case Expression:
		return first.HasLeadingTrivia()
	case bool:
		return first
	default:
		contract.Failf("unexpected value of type %T for first", first)
		return false
	}
}

func exprHasTrailingTrivia(parens syntax.Parentheses, last interface{}) bool {
	if parens.Any() {
		return true
	}
	switch last := last.(type) {
	case Expression:
		return last.HasTrailingTrivia()
	case bool:
		return last
	default:
		contract.Failf("unexpected value of type %T for last", last)
		return false
	}
}

func getExprLeadingTrivia(parens syntax.Parentheses, first interface{}) syntax.TriviaList {
	if parens.Any() {
		return parens.GetLeadingTrivia()
	}
	switch first := first.(type) {
	case Expression:
		return first.GetLeadingTrivia()
	case syntax.Token:
		return first.LeadingTrivia
	}
	return nil
}

func setExprLeadingTrivia(parens syntax.Parentheses, first interface{}, trivia syntax.TriviaList) {
	if parens.Any() {
		parens.SetLeadingTrivia(trivia)
		return
	}
	switch first := first.(type) {
	case Expression:
		first.SetLeadingTrivia(trivia)
	case *syntax.Token:
		first.LeadingTrivia = trivia
	}
}

func getExprTrailingTrivia(parens syntax.Parentheses, last interface{}) syntax.TriviaList {
	if parens.Any() {
		return parens.GetTrailingTrivia()
	}
	switch last := last.(type) {
	case Expression:
		return last.GetTrailingTrivia()
	case syntax.Token:
		return last.TrailingTrivia
	}
	return nil
}

func setExprTrailingTrivia(parens syntax.Parentheses, last interface{}, trivia syntax.TriviaList) {
	if parens.Any() {
		parens.SetTrailingTrivia(trivia)
		return
	}
	switch last := last.(type) {
	case Expression:
		last.SetTrailingTrivia(trivia)
	case *syntax.Token:
		last.TrailingTrivia = trivia
	}
}

type syntaxExpr struct {
	hclsyntax.LiteralValueExpr

	expr Expression
}

func (x syntaxExpr) Value(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	return x.expr.Evaluate(context)
}

type hclExpression struct {
	x Expression
}

func HCLExpression(x Expression) hcl.Expression {
	return hclExpression{x: x}
}

func (x hclExpression) Value(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	return x.x.Evaluate(context)
}

func (x hclExpression) Variables() []hcl.Traversal {
	var variables []hcl.Traversal
	scope := NewRootScope(syntax.None)
	_, diags := VisitExpression(x.x, func(n Expression) (Expression, hcl.Diagnostics) {
		switch n := n.(type) {
		case *AnonymousFunctionExpression:
			scope = scope.Push(syntax.None)
			for _, p := range n.Parameters {
				scope.Define(p.Name, p)
			}
		case *ForExpression:
			scope = scope.Push(syntax.None)
			if n.KeyVariable != nil {
				scope.Define(n.KeyVariable.Name, n.KeyVariable)
			}
			scope.Define(n.ValueVariable.Name, n.ValueVariable)
		}
		return n, nil
	}, func(n Expression) (Expression, hcl.Diagnostics) {
		switch n := n.(type) {
		case *AnonymousFunctionExpression, *ForExpression:
			scope = scope.Pop()
		case *ScopeTraversalExpression:
			if _, isSplatVariable := n.Parts[0].(*SplatVariable); !isSplatVariable {
				if _, defined := scope.BindReference(n.RootName); !defined {
					variables = append(variables, n.Traversal.SimpleSplit().Abs)
				}
			}
		}
		return n, nil
	})
	contract.Assertf(len(diags) == 0, "unexpected diagnostics: %v", diags)
	return variables
}

func (x hclExpression) Range() hcl.Range {
	if syntax := x.x.SyntaxNode(); syntax != nil {
		return syntax.Range()
	}
	return hcl.Range{}
}

func (x hclExpression) StartRange() hcl.Range {
	if syntax := x.x.SyntaxNode(); syntax != nil {
		return syntax.(hcl.Expression).StartRange()
	}
	return hcl.Range{}
}

func operatorPrecedence(op *hclsyntax.Operation) int {
	switch op {
	case hclsyntax.OpLogicalOr:
		return 1
	case hclsyntax.OpLogicalAnd:
		return 2
	case hclsyntax.OpEqual, hclsyntax.OpNotEqual:
		return 3
	case hclsyntax.OpGreaterThan, hclsyntax.OpGreaterThanOrEqual, hclsyntax.OpLessThan, hclsyntax.OpLessThanOrEqual:
		return 4
	case hclsyntax.OpAdd, hclsyntax.OpSubtract:
		return 5
	case hclsyntax.OpMultiply, hclsyntax.OpDivide, hclsyntax.OpModulo:
		return 6
	case hclsyntax.OpNegate, hclsyntax.OpLogicalNot:
		return 7
	default:
		return 8
	}
}

// AnonymousFunctionExpression represents a semantically-analyzed anonymous function expression.
//
// These expressions are not the result of semantically analyzing syntax nodes. Instead, they may be synthesized by
// transforms over the IR for a program (e.g. the Apply transform).
type AnonymousFunctionExpression struct {
	// The signature for the anonymous function.
	Signature StaticFunctionSignature
	// The parameter definitions for the anonymous function.
	Parameters []*Variable

	// The body of the anonymous function.
	Body Expression
}

// SyntaxNode returns the syntax node associated with the body of the anonymous function.
func (x *AnonymousFunctionExpression) SyntaxNode() hclsyntax.Node {
	return x.Body.SyntaxNode()
}

// NodeTokens returns the tokens associated with the body of the anonymous function.
func (x *AnonymousFunctionExpression) NodeTokens() syntax.NodeTokens {
	return x.Body.NodeTokens()
}

// Type returns the type of the anonymous function expression.
//
// TODO: currently this returns the any type. Instead, it should return a function type.
func (x *AnonymousFunctionExpression) Type() Type {
	return DynamicType
}

func (x *AnonymousFunctionExpression) Typecheck(typecheckOperands bool) hcl.Diagnostics {
	var diagnostics hcl.Diagnostics

	if typecheckOperands {
		bodyDiags := x.Body.Typecheck(true)
		diagnostics = append(diagnostics, bodyDiags...)
	}

	return diagnostics
}

func (x *AnonymousFunctionExpression) Evaluate(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	return cty.NilVal, hcl.Diagnostics{cannotEvaluateAnonymousFunctionExpressions()}
}

func (x *AnonymousFunctionExpression) HasLeadingTrivia() bool {
	return x.Body.HasLeadingTrivia()
}

func (x *AnonymousFunctionExpression) HasTrailingTrivia() bool {
	return x.Body.HasTrailingTrivia()
}

func (x *AnonymousFunctionExpression) GetLeadingTrivia() syntax.TriviaList {
	return x.Body.GetLeadingTrivia()
}

func (x *AnonymousFunctionExpression) SetLeadingTrivia(t syntax.TriviaList) {
	x.Body.SetLeadingTrivia(t)
}

func (x *AnonymousFunctionExpression) GetTrailingTrivia() syntax.TriviaList {
	return x.Body.GetTrailingTrivia()
}

func (x *AnonymousFunctionExpression) SetTrailingTrivia(t syntax.TriviaList) {
	x.Body.SetTrailingTrivia(t)
}

func (x *AnonymousFunctionExpression) Format(f fmt.State, c rune) {
	x.print(f, &printer{})
}

func (x *AnonymousFunctionExpression) print(w io.Writer, p *printer) {
	// Print a call to eval.
	p.fprintf(w, "eval(")

	// Print the parameter names.
	for _, v := range x.Parameters {
		p.fprintf(w, "%v, ", v.Name)
	}

	// Print the body and closing paren.
	p.fprintf(w, "%v)", x.Body)
}

func (*AnonymousFunctionExpression) isExpression() {}

// BinaryOpExpression represents a semantically-analyzed binary operation.
type BinaryOpExpression struct {
	// The syntax node associated with the binary operation.
	Syntax *hclsyntax.BinaryOpExpr
	// The tokens associated with the expression, if any.
	Tokens *syntax.BinaryOpTokens

	// The left-hand operand of the operation.
	LeftOperand Expression
	// The operation.
	Operation *hclsyntax.Operation
	// The right-hand operand of the operation.
	RightOperand Expression

	leftType  Type
	rightType Type
	exprType  Type
}

// SyntaxNode returns the syntax node associated with the binary operation.
func (x *BinaryOpExpression) SyntaxNode() hclsyntax.Node {
	if x.Syntax == nil {
		return syntax.None
	}
	return x.Syntax
}

// NodeTokens returns the tokens associated with the binary operation.
func (x *BinaryOpExpression) NodeTokens() syntax.NodeTokens {
	return x.Tokens
}

// LeftOperandType returns the desired type for the left operand of the binary operation.
func (x *BinaryOpExpression) LeftOperandType() Type {
	return x.leftType
}

// RightOperandType returns the desired type for the right operand of the binary operation.
func (x *BinaryOpExpression) RightOperandType() Type {
	return x.rightType
}

// Type returns the type of the binary operation.
func (x *BinaryOpExpression) Type() Type {
	return x.exprType
}

func (x *BinaryOpExpression) Typecheck(typecheckOperands bool) hcl.Diagnostics {
	var diagnostics hcl.Diagnostics

	var rng hcl.Range
	if x.Syntax != nil {
		rng = x.Syntax.Range()
	}

	if typecheckOperands {
		leftDiags := x.LeftOperand.Typecheck(true)
		diagnostics = append(diagnostics, leftDiags...)

		rightDiags := x.RightOperand.Typecheck(true)
		diagnostics = append(diagnostics, rightDiags...)
	}

	// Compute the signature for the operator and typecheck the arguments.
	signature := getOperationSignature(x.Operation)
	contract.Assertf(len(signature.Parameters) == 2,
		"expected binary operator signature to have two parameters, got %v", len(signature.Parameters))

	x.leftType = signature.Parameters[0].Type
	x.rightType = signature.Parameters[1].Type

	typecheckDiags := typecheckArgs(rng, signature, x.LeftOperand, x.RightOperand)
	diagnostics = append(diagnostics, typecheckDiags...)

	x.exprType = liftOperationType(signature.ReturnType, x.LeftOperand, x.RightOperand)
	return diagnostics
}

func (x *BinaryOpExpression) Evaluate(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	syntax := &hclsyntax.BinaryOpExpr{
		LHS: &syntaxExpr{expr: x.LeftOperand},
		Op:  x.Operation,
		RHS: &syntaxExpr{expr: x.RightOperand},
	}
	return syntax.Value(context)
}

func (x *BinaryOpExpression) HasLeadingTrivia() bool {
	return exprHasLeadingTrivia(x.Tokens.GetParentheses(), x.LeftOperand)
}

func (x *BinaryOpExpression) HasTrailingTrivia() bool {
	return exprHasTrailingTrivia(x.Tokens.GetParentheses(), x.RightOperand)
}

func (x *BinaryOpExpression) GetLeadingTrivia() syntax.TriviaList {
	return getExprLeadingTrivia(x.Tokens.GetParentheses(), x.LeftOperand)
}

func (x *BinaryOpExpression) SetLeadingTrivia(t syntax.TriviaList) {
	setExprLeadingTrivia(x.Tokens.GetParentheses(), x.LeftOperand, t)
}

func (x *BinaryOpExpression) GetTrailingTrivia() syntax.TriviaList {
	return getExprTrailingTrivia(x.Tokens.GetParentheses(), x.RightOperand)
}

func (x *BinaryOpExpression) SetTrailingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewBinaryOpTokens(x.Operation)
	}
	setExprTrailingTrivia(x.Tokens.GetParentheses(), x.RightOperand, t)
}

func (x *BinaryOpExpression) Format(f fmt.State, c rune) {
	x.print(f, &printer{})
}

func (x *BinaryOpExpression) print(w io.Writer, p *printer) {
	precedence := operatorPrecedence(x.Operation)
	p.fprintf(w, "%[2](%.[1]*[3]v% [4]v% .[1]*[5]o%[6])",
		precedence,
		x.Tokens.GetParentheses(),
		x.LeftOperand, x.Tokens.GetOperator(x.Operation), x.RightOperand,
		x.Tokens.GetParentheses())
}

func (*BinaryOpExpression) isExpression() {}

// ConditionalExpression represents a semantically-analzed conditional expression (i.e.
// <condition> '?' <true> ':' <false>).
type ConditionalExpression struct {
	// The syntax node associated with the conditional expression.
	Syntax *hclsyntax.ConditionalExpr
	// The tokens associated with the expression, if any.
	Tokens syntax.NodeTokens

	// The condition.
	Condition Expression
	// The result of the expression if the condition evaluates to true.
	TrueResult Expression
	// The result of the expression if the condition evaluates to false.
	FalseResult Expression

	exprType Type
}

// SyntaxNode returns the syntax node associated with the conditional expression.
func (x *ConditionalExpression) SyntaxNode() hclsyntax.Node {
	if x.Syntax == nil {
		return syntax.None
	}
	return x.Syntax
}

// NodeTokens returns the tokens associated with the conditional expression.
func (x *ConditionalExpression) NodeTokens() syntax.NodeTokens {
	return x.Tokens
}

// Type returns the type of the conditional expression.
func (x *ConditionalExpression) Type() Type {
	return x.exprType
}

func (x *ConditionalExpression) Typecheck(typecheckOperands bool) hcl.Diagnostics {
	var diagnostics hcl.Diagnostics

	if typecheckOperands {
		conditionDiags := x.Condition.Typecheck(true)
		diagnostics = append(diagnostics, conditionDiags...)

		trueDiags := x.TrueResult.Typecheck(true)
		diagnostics = append(diagnostics, trueDiags...)

		falseDiags := x.FalseResult.Typecheck(true)
		diagnostics = append(diagnostics, falseDiags...)
	}

	// Compute the type of the result.
	resultType, _ := UnifyTypes(x.TrueResult.Type(), x.FalseResult.Type())

	// Typecheck the condition expression.
	if InputType(BoolType).ConversionFrom(x.Condition.Type()) == NoConversion {
		diagnostics = append(diagnostics, ExprNotConvertible(InputType(BoolType), x.Condition))
	}

	x.exprType = liftOperationType(resultType, x.Condition)
	return diagnostics
}

func (x *ConditionalExpression) Evaluate(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	syntax := &hclsyntax.ConditionalExpr{
		Condition:   &syntaxExpr{expr: x.Condition},
		TrueResult:  &syntaxExpr{expr: x.TrueResult},
		FalseResult: &syntaxExpr{expr: x.FalseResult},
	}
	return syntax.Value(context)
}

func (x *ConditionalExpression) HasLeadingTrivia() bool {
	switch tokens := x.Tokens.(type) {
	case *syntax.ConditionalTokens:
		if tokens.Parentheses.Any() {
			return true
		}
	case *syntax.TemplateConditionalTokens:
		return len(tokens.OpenIf.LeadingTrivia) != 0
	}
	return x.Condition.HasLeadingTrivia()
}

func (x *ConditionalExpression) HasTrailingTrivia() bool {
	switch tokens := x.Tokens.(type) {
	case *syntax.ConditionalTokens:
		if tokens.Parentheses.Any() {
			return true
		}
	case *syntax.TemplateConditionalTokens:
		return len(tokens.CloseEndif.TrailingTrivia) != 0
	}
	return x.FalseResult.HasTrailingTrivia()
}

func (x *ConditionalExpression) GetLeadingTrivia() syntax.TriviaList {
	switch tokens := x.Tokens.(type) {
	case *syntax.ConditionalTokens:
		if tokens.Parentheses.Any() {
			return tokens.Parentheses.GetLeadingTrivia()
		}
	case *syntax.TemplateConditionalTokens:
		return tokens.OpenIf.LeadingTrivia
	}
	return x.Condition.GetLeadingTrivia()
}

func (x *ConditionalExpression) SetLeadingTrivia(t syntax.TriviaList) {
	switch tokens := x.Tokens.(type) {
	case *syntax.ConditionalTokens:
		if tokens.Parentheses.Any() {
			tokens.Parentheses.SetLeadingTrivia(t)
			return
		}
	case *syntax.TemplateConditionalTokens:
		tokens.OpenIf.LeadingTrivia = t
		return
	}
	x.Condition.SetLeadingTrivia(t)
}

func (x *ConditionalExpression) GetTrailingTrivia() syntax.TriviaList {
	switch tokens := x.Tokens.(type) {
	case *syntax.ConditionalTokens:
		if tokens.Parentheses.Any() {
			return tokens.Parentheses.GetTrailingTrivia()
		}
	case *syntax.TemplateConditionalTokens:
		return tokens.CloseEndif.TrailingTrivia
	}
	return x.FalseResult.GetTrailingTrivia()
}

func (x *ConditionalExpression) SetTrailingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewConditionalTokens()
	}
	switch tokens := x.Tokens.(type) {
	case *syntax.ConditionalTokens:
		if tokens.Parentheses.Any() {
			tokens.Parentheses.SetTrailingTrivia(t)
			return
		}
	case *syntax.TemplateConditionalTokens:
		tokens.CloseEndif.TrailingTrivia = t
		return
	}
	x.FalseResult.SetTrailingTrivia(t)
}

func (x *ConditionalExpression) Format(f fmt.State, c rune) {
	x.print(f, &printer{})
}

func (x *ConditionalExpression) print(w io.Writer, p *printer) {
	tokens := x.Tokens
	if tokens == nil {
		tokens = syntax.NewConditionalTokens()
	}

	switch tokens := tokens.(type) {
	case *syntax.ConditionalTokens:
		p.fprintf(w, "%(%v% v% v% v% v%)",
			tokens.Parentheses,
			x.Condition, tokens.QuestionMark, x.TrueResult, tokens.Colon, x.FalseResult,
			tokens.Parentheses)
	case *syntax.TemplateConditionalTokens:
		p.fprintf(w, "%v%v% v%v%v", tokens.OpenIf, tokens.If, x.Condition, tokens.CloseIf, x.TrueResult)
		if tokens.Else != nil {
			p.fprintf(w, "%v%v%v%v", tokens.OpenElse, tokens.Else, tokens.CloseElse, x.FalseResult)
		}
		p.fprintf(w, "%v%v%v", tokens.OpenEndif, tokens.Endif, tokens.CloseEndif)
	}
}

func (*ConditionalExpression) isExpression() {}

// ErrorExpression represents an expression that could not be bound due to an error.
type ErrorExpression struct {
	// The syntax node associated with the error, if any.
	Syntax hclsyntax.Node
	// The tokens associated with the error.
	Tokens syntax.NodeTokens
	// The message associated with the error.
	Message string

	exprType Type
}

// SyntaxNode returns the syntax node associated with the error expression.
func (x *ErrorExpression) SyntaxNode() hclsyntax.Node {
	return x.Syntax
}

// NodeTokens returns the tokens associated with the error expression.
func (x *ErrorExpression) NodeTokens() syntax.NodeTokens {
	return x.Tokens
}

// Type returns the type of the error expression.
func (x *ErrorExpression) Type() Type {
	return x.exprType
}

func (x *ErrorExpression) Typecheck(typecheckOperands bool) hcl.Diagnostics {
	return nil
}

func (x *ErrorExpression) Evaluate(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	return cty.DynamicVal, hcl.Diagnostics{{
		Severity: hcl.DiagError,
		Summary:  x.Message,
	}}
}

func (x *ErrorExpression) HasLeadingTrivia() bool {
	return false
}

func (x *ErrorExpression) HasTrailingTrivia() bool {
	return false
}

func (x *ErrorExpression) GetLeadingTrivia() syntax.TriviaList {
	return nil
}

func (x *ErrorExpression) SetLeadingTrivia(t syntax.TriviaList) {
}

func (x *ErrorExpression) GetTrailingTrivia() syntax.TriviaList {
	return nil
}

func (x *ErrorExpression) SetTrailingTrivia(t syntax.TriviaList) {
}

func (x *ErrorExpression) Format(f fmt.State, c rune) {
	x.print(f, &printer{})
}

func (x *ErrorExpression) print(w io.Writer, p *printer) {
	p.fprintf(w, "error(%q)", x.Message)
}

func (*ErrorExpression) isExpression() {}

// ForExpression represents a semantically-analyzed for expression.
type ForExpression struct {
	fmt.Formatter

	// The syntax node associated with the for expression.
	Syntax *hclsyntax.ForExpr
	// The tokens associated with the expression, if any.
	Tokens syntax.NodeTokens

	// The key variable, if any.
	KeyVariable *Variable
	// The value variable.
	ValueVariable *Variable

	// The collection being iterated.
	Collection Expression
	// The expression that generates the keys of the result, if any. If this field is non-nil, the result is a map.
	Key Expression
	// The expression that generates the values of the result.
	Value Expression
	// The condition that filters the items of the result, if any.
	Condition Expression

	// True if the value expression is being grouped.
	Group bool

	// Whether the collection type should be strictly type-checked
	// When true, unsupported collection types will result in an error
	// otherwise a warning will be emitted
	StrictCollectionTypechecking bool

	exprType Type
}

// SyntaxNode returns the syntax node associated with the for expression.
func (x *ForExpression) SyntaxNode() hclsyntax.Node {
	if x.Syntax == nil {
		return syntax.None
	}
	return x.Syntax
}

// NodeTokens returns the tokens associated with the for expression.
func (x *ForExpression) NodeTokens() syntax.NodeTokens {
	return x.Tokens
}

// Type returns the type of the for expression.
func (x *ForExpression) Type() Type {
	return x.exprType
}

func (x *ForExpression) typecheck(typecheckCollection, typecheckOperands bool) hcl.Diagnostics {
	var diagnostics hcl.Diagnostics

	var rng hcl.Range
	if x.Syntax != nil {
		rng = x.Syntax.CollExpr.Range()
	}

	if typecheckOperands {
		collectionDiags := x.Collection.Typecheck(true)
		diagnostics = append(diagnostics, collectionDiags...)
	}

	if typecheckCollection {
		keyType, valueType, kvDiags := GetCollectionTypes(x.Collection.Type(), rng, x.StrictCollectionTypechecking)
		diagnostics = append(diagnostics, kvDiags...)

		if x.KeyVariable != nil {
			x.KeyVariable.VariableType = keyType
		}
		x.ValueVariable.VariableType = valueType
	}

	if typecheckOperands {
		if x.Key != nil {
			keyDiags := x.Key.Typecheck(true)
			diagnostics = append(diagnostics, keyDiags...)
		}

		valueDiags := x.Value.Typecheck(true)
		diagnostics = append(diagnostics, valueDiags...)

		if x.Condition != nil {
			conditionDiags := x.Condition.Typecheck(true)
			diagnostics = append(diagnostics, conditionDiags...)
		}
	}

	if x.Key != nil {
		// A key expression is only present when producing a map. Key types must therefore be strings.
		if !InputType(StringType).ConversionFrom(x.Key.Type()).Exists() {
			diagnostics = append(diagnostics, ExprNotConvertible(InputType(StringType), x.Key))
		}
	}

	if x.Condition != nil {
		if !InputType(BoolType).ConversionFrom(x.Condition.Type()).Exists() {
			diagnostics = append(diagnostics, ExprNotConvertible(InputType(BoolType), x.Condition))
		}
	}

	// If there is a key expression, we are producing a map. Otherwise, we are producing an list. In either case, wrap
	// the result type in the same set of eventuals and optionals present in the collection type.
	var resultType Type
	if x.Key != nil {
		valueType := x.Value.Type()
		if x.Group {
			valueType = NewListType(valueType)
		}
		resultType = wrapIterableResultType(x.Collection.Type(), NewMapType(valueType))
	} else {
		resultType = wrapIterableResultType(x.Collection.Type(), NewListType(x.Value.Type()))
	}

	// If either the key expression or the condition expression is eventual, the result is eventual: each of these
	// values is required to determine which items are present in the result.
	var liftArgs []Expression
	if x.Key != nil {
		liftArgs = append(liftArgs, x.Key)
	}
	if x.Condition != nil {
		liftArgs = append(liftArgs, x.Condition)
	}

	x.exprType = liftOperationType(resultType, liftArgs...)
	return diagnostics
}

func (x *ForExpression) Typecheck(typecheckOperands bool) hcl.Diagnostics {
	return x.typecheck(true, typecheckOperands)
}

func (x *ForExpression) Evaluate(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	syntax := &hclsyntax.ForExpr{
		ValVar:   x.ValueVariable.Name,
		CollExpr: &syntaxExpr{expr: x.Collection},
		ValExpr:  &syntaxExpr{expr: x.Value},
		Group:    x.Group,
	}
	if x.KeyVariable != nil {
		syntax.KeyVar = x.KeyVariable.Name
	}
	if x.Key != nil {
		syntax.KeyExpr = &syntaxExpr{expr: x.Key}
	}
	if x.Condition != nil {
		syntax.CondExpr = &syntaxExpr{expr: x.Condition}
	}
	return syntax.Value(context)
}

func (x *ForExpression) HasLeadingTrivia() bool {
	return x.Tokens != nil
}

func (x *ForExpression) HasTrailingTrivia() bool {
	return x.Tokens != nil
}

func (x *ForExpression) GetLeadingTrivia() syntax.TriviaList {
	switch tokens := x.Tokens.(type) {
	case *syntax.ForTokens:
		return getExprLeadingTrivia(tokens.Parentheses, tokens.Open)
	case *syntax.TemplateForTokens:
		return tokens.OpenFor.LeadingTrivia
	default:
		return nil
	}
}

func (x *ForExpression) SetLeadingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		keyVariable := ""
		if x.KeyVariable != nil {
			keyVariable = x.KeyVariable.Name
		}
		x.Tokens = syntax.NewForTokens(keyVariable, x.ValueVariable.Name, x.Key != nil, x.Group, x.Condition != nil)
	}
	switch tokens := x.Tokens.(type) {
	case *syntax.ForTokens:
		setExprLeadingTrivia(tokens.Parentheses, &tokens.Open, t)
	case *syntax.TemplateForTokens:
		tokens.OpenFor.LeadingTrivia = t
	}
}

func (x *ForExpression) GetTrailingTrivia() syntax.TriviaList {
	switch tokens := x.Tokens.(type) {
	case *syntax.ForTokens:
		return getExprTrailingTrivia(tokens.Parentheses, tokens.Close)
	case *syntax.TemplateForTokens:
		return tokens.CloseEndfor.TrailingTrivia
	default:
		return nil
	}
}

func (x *ForExpression) SetTrailingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		keyVariable := ""
		if x.KeyVariable != nil {
			keyVariable = x.KeyVariable.Name
		}
		x.Tokens = syntax.NewForTokens(keyVariable, x.ValueVariable.Name, x.Key != nil, x.Group, x.Condition != nil)
	}
	switch tokens := x.Tokens.(type) {
	case *syntax.ForTokens:
		setExprTrailingTrivia(tokens.Parentheses, &tokens.Close, t)
	case *syntax.TemplateForTokens:
		tokens.CloseEndfor.TrailingTrivia = t
	}
}

func (x *ForExpression) Format(f fmt.State, c rune) {
	x.print(f, &printer{})
}

func (x *ForExpression) print(w io.Writer, p *printer) {
	tokens := x.Tokens
	if tokens == nil {
		keyVariable := ""
		if x.KeyVariable != nil {
			keyVariable = x.KeyVariable.Name
		}
		syntax.NewForTokens(keyVariable, x.ValueVariable.Name, x.Key != nil, x.Group, x.Condition != nil)
	}

	switch tokens := tokens.(type) {
	case *syntax.ForTokens:
		// Print the opening rune and the for token.
		p.fprintf(w, "%(%v%v", tokens.Parentheses, tokens.Open, tokens.For)

		// Print the key variable, if any.
		if x.KeyVariable != nil {
			keyToken := tokens.Key
			if x.KeyVariable != nil && keyToken == nil {
				keyToken = &syntax.Token{Raw: hclsyntax.Token{Type: hclsyntax.TokenIdent}}
			}

			key := identToken(*keyToken, x.KeyVariable.Name)
			p.fprintf(w, "% v%v", key, tokens.Comma)
		}

		// Print the value variable, the in token, the collection expression, and the colon.
		value := identToken(tokens.Value, x.ValueVariable.Name)
		p.fprintf(w, "% v% v% v%v", value, tokens.In, x.Collection, tokens.Colon)

		// Print the key expression and arrow token, if any.
		if x.Key != nil {
			p.fprintf(w, "% v% v", x.Key, tokens.Arrow)
		}

		// Print the value expression.
		p.fprintf(w, "% v", x.Value)

		// Print the group token, if any.
		if x.Group {
			p.fprintf(w, "%v", tokens.Group)
		}

		// Print the if token and the condition, if any.
		if x.Condition != nil {
			p.fprintf(w, "% v% v", tokens.If, x.Condition)
		}

		// Print the closing rune.
		p.fprintf(w, "%v%)", tokens.Close, tokens.Parentheses)
	case *syntax.TemplateForTokens:
		// Print the opening sequence.
		p.fprintf(w, "%v%v", tokens.OpenFor, tokens.For)

		// Print the key variable, if any.
		if x.KeyVariable != nil {
			keyToken := tokens.Key
			if x.KeyVariable != nil && keyToken == nil {
				keyToken = &syntax.Token{Raw: hclsyntax.Token{Type: hclsyntax.TokenIdent}}
			}

			key := identToken(*keyToken, x.KeyVariable.Name)
			p.fprintf(w, "% v%v", key, tokens.Comma)
		}

		// Print the value variable, the in token, the collection expression, the control sequence terminator, the
		// value expression, and the closing sequence.
		p.fprintf(w, "% v% v% v%v%v%v%v%v",
			identToken(tokens.Value, x.ValueVariable.Name), tokens.In, x.Collection, tokens.CloseFor,
			x.Value,
			tokens.OpenEndfor, tokens.Endfor, tokens.CloseEndfor)
	}
}

func (*ForExpression) isExpression() {}

// FunctionCallExpression represents a semantically-analyzed function call expression.
type FunctionCallExpression struct {
	// The syntax node associated with the function call expression.
	Syntax *hclsyntax.FunctionCallExpr
	// The tokens associated with the expression, if any.
	Tokens *syntax.FunctionCallTokens

	// The name of the called function.
	Name string
	// The signature of the called function.
	Signature StaticFunctionSignature
	// The arguments to the function call.
	Args []Expression
	// ExpandFinal indicates that the final argument should be a tuple, list, or set whose elements will be passed as
	// individual arguments to the function.
	ExpandFinal bool
}

// SyntaxNode returns the syntax node associated with the function call expression.
func (x *FunctionCallExpression) SyntaxNode() hclsyntax.Node {
	if x.Syntax == nil {
		return syntax.None
	}
	return x.Syntax
}

// NodeTokens returns the tokens associated with the function call expression.
func (x *FunctionCallExpression) NodeTokens() syntax.NodeTokens {
	return x.Tokens
}

// Type returns the type of the function call expression.
func (x *FunctionCallExpression) Type() Type {
	return x.Signature.ReturnType
}

func (x *FunctionCallExpression) Typecheck(typecheckOperands bool) hcl.Diagnostics {
	var diagnostics hcl.Diagnostics

	if typecheckOperands {
		for _, arg := range x.Args {
			argDiagnostics := arg.Typecheck(true)
			diagnostics = append(diagnostics, argDiagnostics...)
		}
	}

	var rng hcl.Range
	if x.Syntax != nil {
		rng = x.Syntax.Range()
	}

	// Typecheck the function's arguments.
	typecheckDiags := typecheckArgs(rng, x.Signature, x.Args...)
	diagnostics = append(diagnostics, typecheckDiags...)

	// Unless the function is already automatically using an
	// Output-returning version, modify the signature to account
	// for automatic lifting to Promise or Output.
	_, isOutput := x.Signature.ReturnType.(*OutputType)
	if !isOutput {
		x.Signature.ReturnType = liftOperationType(x.Signature.ReturnType, x.Args...)
	}
	return diagnostics
}

func (x *FunctionCallExpression) Evaluate(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	syntax := &hclsyntax.FunctionCallExpr{
		Name:        x.Name,
		Args:        make([]hclsyntax.Expression, len(x.Args)),
		ExpandFinal: x.ExpandFinal,
	}
	for i, arg := range x.Args {
		syntax.Args[i] = &syntaxExpr{expr: arg}
	}
	return syntax.Value(context)
}

func (x *FunctionCallExpression) HasLeadingTrivia() bool {
	return exprHasLeadingTrivia(x.Tokens.GetParentheses(), x.Tokens != nil)
}

func (x *FunctionCallExpression) HasTrailingTrivia() bool {
	return exprHasTrailingTrivia(x.Tokens.GetParentheses(), x.Tokens != nil)
}

func (x *FunctionCallExpression) GetLeadingTrivia() syntax.TriviaList {
	return getExprLeadingTrivia(x.Tokens.GetParentheses(), x.Tokens.GetName(x.Name))
}

func (x *FunctionCallExpression) SetLeadingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewFunctionCallTokens(x.Name, len(x.Args))
	}
	setExprLeadingTrivia(x.Tokens.Parentheses, &x.Tokens.Name, t)
}

func (x *FunctionCallExpression) GetTrailingTrivia() syntax.TriviaList {
	return getExprTrailingTrivia(x.Tokens.GetParentheses(), x.Tokens.GetCloseParen())
}

func (x *FunctionCallExpression) SetTrailingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewFunctionCallTokens(x.Name, len(x.Args))
	}
	setExprTrailingTrivia(x.Tokens.Parentheses, &x.Tokens.CloseParen, t)
}

func (x *FunctionCallExpression) Format(f fmt.State, c rune) {
	x.print(f, &printer{})
}

func (x *FunctionCallExpression) print(w io.Writer, p *printer) {
	// Print the name and opening parenthesis.
	p.fprintf(w, "%(%v%v", x.Tokens.GetParentheses(), x.Tokens.GetName(x.Name), x.Tokens.GetOpenParen())

	// Print each argument and its comma.
	commas := x.Tokens.GetCommas(len(x.Args))
	for i, arg := range x.Args {
		if i == 0 {
			p.fprintf(w, "%v", arg)
		} else {
			p.fprintf(w, "% v", arg)
		}

		if i < len(x.Args)-1 {
			var comma syntax.Token
			if i < len(commas) {
				comma = commas[i]
			}
			p.fprintf(w, "%v", comma)
		}
	}

	// If there were commas left over, print the trivia for each.
	if len(x.Args) > 0 && len(x.Args)-1 <= len(commas) {
		for _, comma := range commas[len(x.Args)-1:] {
			p.fprintf(w, "%v", comma.AllTrivia().CollapseWhitespace())
		}
	}

	// Print the closing parenthesis.
	p.fprintf(w, "%v%)", x.Tokens.GetCloseParen(), x.Tokens.GetParentheses())
}

func (*FunctionCallExpression) isExpression() {}

// IndexExpression represents a semantically-analyzed index expression.
type IndexExpression struct {
	// The syntax node associated with the index expression.
	Syntax *hclsyntax.IndexExpr
	// The tokens associated with the expression, if any.
	Tokens *syntax.IndexTokens

	// The collection being indexed.
	Collection Expression
	// The index key.
	Key Expression
	// Whether the collection type should be strictly type-checked
	// When true, unsupported collection types will result in an error
	// otherwise a warning will be emitted
	StrictCollectionTypechecking bool

	keyType  Type
	exprType Type
}

// SyntaxNode returns the syntax node associated with the index expression.
func (x *IndexExpression) SyntaxNode() hclsyntax.Node {
	if x.Syntax == nil {
		return syntax.None
	}
	return x.Syntax
}

// NodeTokens returns the tokens associated with the index expression.
func (x *IndexExpression) NodeTokens() syntax.NodeTokens {
	return x.Tokens
}

// KeyType returns the expected type of the index expression's key.
func (x *IndexExpression) KeyType() Type {
	return x.keyType
}

// Type returns the type of the index expression.
func (x *IndexExpression) Type() Type {
	return x.exprType
}

func (x *IndexExpression) Typecheck(typecheckOperands bool) hcl.Diagnostics {
	var diagnostics hcl.Diagnostics

	if typecheckOperands {
		collectionDiags := x.Collection.Typecheck(true)
		diagnostics = append(diagnostics, collectionDiags...)

		keyDiags := x.Key.Typecheck(true)
		diagnostics = append(diagnostics, keyDiags...)
	}

	var rng hcl.Range
	if x.Syntax != nil {
		rng = x.Syntax.Collection.Range()
	}

	keyType, valueType, kvDiags := GetCollectionTypes(x.Collection.Type(), rng, x.StrictCollectionTypechecking)
	diagnostics = append(diagnostics, kvDiags...)
	x.keyType = keyType

	if lit, ok := x.Key.(*LiteralValueExpression); ok {
		traverser := hcl.TraverseIndex{
			Key: lit.Value,
		}
		valueType, traverseDiags := x.Collection.Type().Traverse(traverser)
		if len(traverseDiags) == 0 {
			x.exprType = valueType.(Type)
			return diagnostics
		}
	}

	if !InputType(keyType).ConversionFrom(x.Key.Type()).Exists() {
		diagnostics = append(diagnostics, ExprNotConvertible(InputType(keyType), x.Key))
	}

	resultType := wrapIterableResultType(x.Collection.Type(), valueType)
	x.exprType = liftOperationType(resultType, x.Key)
	return diagnostics
}

func (x *IndexExpression) Evaluate(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	syntax := &hclsyntax.IndexExpr{
		Collection: &syntaxExpr{expr: x.Collection},
		Key:        &syntaxExpr{expr: x.Key},
	}
	return syntax.Value(context)
}

func (x *IndexExpression) HasLeadingTrivia() bool {
	return exprHasLeadingTrivia(x.Tokens.GetParentheses(), x.Collection)
}

func (x *IndexExpression) HasTrailingTrivia() bool {
	return exprHasTrailingTrivia(x.Tokens.GetParentheses(), x.Tokens != nil)
}

func (x *IndexExpression) GetLeadingTrivia() syntax.TriviaList {
	return getExprLeadingTrivia(x.Tokens.GetParentheses(), x.Tokens.GetOpenBracket())
}

func (x *IndexExpression) SetLeadingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewIndexTokens()
	}
	setExprLeadingTrivia(x.Tokens.Parentheses, x.Collection, t)
}

func (x *IndexExpression) GetTrailingTrivia() syntax.TriviaList {
	return getExprTrailingTrivia(x.Tokens.GetParentheses(), x.Tokens.GetCloseBracket())
}

func (x *IndexExpression) SetTrailingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewIndexTokens()
	}
	setExprTrailingTrivia(x.Tokens.Parentheses, &x.Tokens.CloseBracket, t)
}

func (x *IndexExpression) Format(f fmt.State, c rune) {
	x.print(f, &printer{})
}

func (x *IndexExpression) print(w io.Writer, p *printer) {
	p.fprintf(w, "%(%v%v%v%v%)",
		x.Tokens.GetParentheses(),
		x.Collection, x.Tokens.GetOpenBracket(), x.Key, x.Tokens.GetCloseBracket(),
		x.Tokens.GetParentheses())
}

func (*IndexExpression) isExpression() {}

func literalText(value cty.Value, rawBytes []byte, escaped, quoted bool) string {
	if len(rawBytes) > 0 {
		parsed, diags := hclsyntax.ParseExpression(rawBytes, "", hcl.Pos{})
		if !diags.HasErrors() {
			if lit, ok := parsed.(*hclsyntax.LiteralValueExpr); ok && lit.Val.RawEquals(value) {
				return string(rawBytes)
			}
		}
	}

	switch value.Type() {
	case cty.Bool:
		if value.True() {
			return "true"
		}
		return "false"
	case cty.Number:
		bf := value.AsBigFloat()
		i, acc := bf.Int64()
		if acc == big.Exact {
			return strconv.FormatInt(i, 10)
		}
		d, _ := bf.Float64()
		return fmt.Sprintf("%g", d)
	case cty.String:
		if !escaped {
			return value.AsString()
		}
		s := escapeString(value.AsString())
		if quoted {
			return fmt.Sprintf(`"%s"`, s)
		}
		return s
	default:
		panic(fmt.Errorf("unexpected literal type %v", value.Type().FriendlyName()))
	}
}

func escapeString(s string) string {
	// escape special characters
	s = strconv.Quote(s)
	s = s[1 : len(s)-1] // Remove surrounding double quote (`"`)

	// Escape `${`
	runes := []rune(s)
	out := slice.Prealloc[rune](len(runes))
	for i, r := range runes {
		next := func() rune {
			if i >= len(runes)-1 {
				return 0
			}
			return runes[i+1]
		}
		if r == '$' && next() == '{' {
			out = append(out, '$')
		} else if r == '%' && next() == '{' {
			out = append(out, '%')
		}
		out = append(out, r)
	}
	return string(out)
}

// LiteralValueExpression represents a semantically-analyzed literal value expression.
type LiteralValueExpression struct {
	// The syntax node associated with the literal value expression.
	Syntax *hclsyntax.LiteralValueExpr
	// The tokens associated with the expression, if any.
	Tokens *syntax.LiteralValueTokens

	// The value of the expression.
	Value cty.Value

	exprType Type
}

// SyntaxNode returns the syntax node associated with the literal value expression.
func (x *LiteralValueExpression) SyntaxNode() hclsyntax.Node {
	if x.Syntax == nil {
		return syntax.None
	}
	return x.Syntax
}

// NodeTokens returns the tokens associated with the literal value expression.
func (x *LiteralValueExpression) NodeTokens() syntax.NodeTokens {
	return x.Tokens
}

// Type returns the type of the literal value expression.
func (x *LiteralValueExpression) Type() Type {
	if x.exprType == nil {
		typ := ctyTypeToType(x.Value.Type(), false)
		x.exprType = NewConstType(typ, x.Value)
	}
	return x.exprType
}

func (x *LiteralValueExpression) Typecheck(typecheckOperands bool) hcl.Diagnostics {
	var diagnostics hcl.Diagnostics

	typ := NoneType
	if !x.Value.IsNull() {
		typ = ctyTypeToType(x.Value.Type(), false)
	}

	switch {
	case typ == NoneType || typ == StringType || typ == IntType || typ == NumberType || typ == BoolType:
		// OK
		typ = NewConstType(typ, x.Value)
	default:
		var rng hcl.Range
		if x.Syntax != nil {
			rng = x.Syntax.Range()
		}
		typ, diagnostics = DynamicType, hcl.Diagnostics{unsupportedLiteralValue(x.Value, rng)}
	}

	x.exprType = typ
	return diagnostics
}

func (x *LiteralValueExpression) Evaluate(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	syntax := &hclsyntax.LiteralValueExpr{
		Val: x.Value,
	}
	return syntax.Value(context)
}

func (x *LiteralValueExpression) HasLeadingTrivia() bool {
	return exprHasLeadingTrivia(x.Tokens.GetParentheses(), x.Tokens != nil)
}

func (x *LiteralValueExpression) HasTrailingTrivia() bool {
	return exprHasTrailingTrivia(x.Tokens.GetParentheses(), x.Tokens != nil)
}

func (x *LiteralValueExpression) GetLeadingTrivia() syntax.TriviaList {
	if v := x.Tokens.GetValue(x.Value); len(v) > 0 {
		return getExprLeadingTrivia(x.Tokens.GetParentheses(), v[0])
	}
	return getExprLeadingTrivia(x.Tokens.GetParentheses(), nil)
}

func (x *LiteralValueExpression) SetLeadingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewLiteralValueTokens(x.Value)
	}
	setExprLeadingTrivia(x.Tokens.Parentheses, &x.Tokens.Value[0], t)
}

func (x *LiteralValueExpression) GetTrailingTrivia() syntax.TriviaList {
	if v := x.Tokens.GetValue(x.Value); len(v) > 0 {
		return getExprTrailingTrivia(x.Tokens.GetParentheses(), v[len(v)-1])
	}
	return getExprTrailingTrivia(x.Tokens.GetParentheses(), nil)
}

func (x *LiteralValueExpression) SetTrailingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewLiteralValueTokens(x.Value)
	}
	setExprTrailingTrivia(x.Tokens.Parentheses, &x.Tokens.Value[len(x.Tokens.Value)-1], t)
}

func (x *LiteralValueExpression) Format(f fmt.State, c rune) {
	x.print(f, &printer{})
}

func (x *LiteralValueExpression) printLit(w io.Writer, p *printer, escaped bool) {
	// Literals are... odd. They may be composed of multiple tokens, but those tokens should never contain interior
	// trivia.

	var leading, trailing syntax.TriviaList
	var rawBytes []byte
	if toks := x.Tokens.GetValue(x.Value); len(toks) > 0 {
		leading, trailing = toks[0].LeadingTrivia, toks[len(toks)-1].TrailingTrivia

		for _, t := range toks {
			rawBytes = append(rawBytes, t.Raw.Bytes...)
		}
	}

	p.fprintf(w, "%(%v%v%v%)",
		x.Tokens.GetParentheses(),
		leading, literalText(x.Value, rawBytes, escaped, false), trailing,
		x.Tokens.GetParentheses())
}

func (x *LiteralValueExpression) print(w io.Writer, p *printer) {
	x.printLit(w, p, false)
}

func (*LiteralValueExpression) isExpression() {}

// ObjectConsItem records a key-value pair that is part of object construction expression.
type ObjectConsItem struct {
	// The key.
	Key Expression
	// The value.
	Value Expression
}

// ObjectConsExpression represents a semantically-analyzed object construction expression.
type ObjectConsExpression struct {
	// The syntax node associated with the object construction expression.
	Syntax *hclsyntax.ObjectConsExpr
	// The tokens associated with the expression, if any.
	Tokens *syntax.ObjectConsTokens

	// The items that comprise the object construction expression.
	Items []ObjectConsItem

	exprType Type
}

// SyntaxNode returns the syntax node associated with the object construction expression.
func (x *ObjectConsExpression) SyntaxNode() hclsyntax.Node {
	if x.Syntax == nil {
		return syntax.None
	}
	return x.Syntax
}

// NodeTokens returns the tokens associated with the object construction expression.
func (x *ObjectConsExpression) NodeTokens() syntax.NodeTokens {
	return x.Tokens
}

// Type returns the type of the object construction expression.
func (x *ObjectConsExpression) Type() Type {
	return x.exprType
}

func (x *ObjectConsExpression) WithType(updateType func(Type) *ObjectConsExpression) *ObjectConsExpression {
	return updateType(x.exprType)
}

func (x *ObjectConsExpression) Typecheck(typecheckOperands bool) hcl.Diagnostics {
	var diagnostics hcl.Diagnostics

	keys := slice.Prealloc[Expression](len(x.Items))
	for _, item := range x.Items {
		if typecheckOperands {
			keyDiags := item.Key.Typecheck(true)
			diagnostics = append(diagnostics, keyDiags...)

			valDiags := item.Value.Typecheck(true)
			diagnostics = append(diagnostics, valDiags...)
		}

		keys = append(keys, item.Key)
		if !InputType(StringType).ConversionFrom(item.Key.Type()).Exists() {
			diagnostics = append(diagnostics, objectKeysMustBeStrings(item.Key))
		}
	}

	// Attempt to build an object type out of the result. If there are any attribute names that come from variables,
	// type the result as map(unify(propertyTypes)).
	properties, isMapType, types := map[string]Type{}, false, []Type{}
	for _, item := range x.Items {
		types = append(types, item.Value.Type())

		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 {
				properties[key.AsString()] = item.Value.Type()
				continue
			}
		}
		isMapType = true
	}
	var typ Type
	if isMapType {
		elementType, _ := UnifyTypes(types...)
		typ = NewMapType(elementType)
	} else {
		typ = NewObjectType(properties)
	}

	x.exprType = liftOperationType(typ, keys...)
	return diagnostics
}

func (x *ObjectConsExpression) Evaluate(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	syntax := &hclsyntax.ObjectConsExpr{
		Items: make([]hclsyntax.ObjectConsItem, len(x.Items)),
	}
	for i, item := range x.Items {
		syntax.Items[i] = hclsyntax.ObjectConsItem{
			KeyExpr:   &syntaxExpr{expr: item.Key},
			ValueExpr: &syntaxExpr{expr: item.Value},
		}
	}
	return syntax.Value(context)
}

func (x *ObjectConsExpression) HasLeadingTrivia() bool {
	return exprHasLeadingTrivia(x.Tokens.GetParentheses(), x.Tokens != nil)
}

func (x *ObjectConsExpression) HasTrailingTrivia() bool {
	return exprHasTrailingTrivia(x.Tokens.GetParentheses(), x.Tokens != nil)
}

func (x *ObjectConsExpression) GetLeadingTrivia() syntax.TriviaList {
	return getExprLeadingTrivia(x.Tokens.GetParentheses(), x.Tokens.GetOpenBrace(len(x.Items)))
}

func (x *ObjectConsExpression) SetLeadingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewObjectConsTokens(len(x.Items))
	}
	setExprLeadingTrivia(x.Tokens.Parentheses, &x.Tokens.OpenBrace, t)
}

func (x *ObjectConsExpression) GetTrailingTrivia() syntax.TriviaList {
	return getExprTrailingTrivia(x.Tokens.GetParentheses(), x.Tokens.GetCloseBrace())
}

func (x *ObjectConsExpression) SetTrailingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewObjectConsTokens(len(x.Items))
	}
	setExprTrailingTrivia(x.Tokens.Parentheses, &x.Tokens.CloseBrace, t)
}

func (x *ObjectConsExpression) Format(f fmt.State, c rune) {
	x.print(f, &printer{})
}

func (x *ObjectConsExpression) print(w io.Writer, p *printer) {
	// Print the opening brace.
	p.fprintf(w, "%(%v", x.Tokens.GetParentheses(), x.Tokens.GetOpenBrace(len(x.Items)))

	// Print the items.
	isMultiLine, trailingNewline := false, false
	p.indented(func() {
		items := x.Tokens.GetItems(len(x.Items))
		for i, item := range x.Items {
			tokens := syntax.NewObjectConsItemTokens(i == len(x.Items)-1)
			if i < len(items) {
				tokens = items[i]
			}

			if item.Key.HasLeadingTrivia() {
				if _, i := item.Key.GetLeadingTrivia().Index("\n"); i != -1 {
					isMultiLine = true
				}
			} else if len(items) > 1 {
				isMultiLine = true
				p.fprintf(w, "\n%s", p.indent)
			}
			p.fprintf(w, "%v% v% v", item.Key, tokens.Equals, item.Value)

			if tokens.Comma != nil {
				p.fprintf(w, "%v", tokens.Comma)
			}

			if isMultiLine && i == len(items)-1 {
				trailingTrivia := item.Value.GetTrailingTrivia()
				if tokens.Comma != nil {
					trailingTrivia = tokens.Comma.TrailingTrivia
				}
				trailingNewline = trailingTrivia.EndsOnNewLine()
			}
		}

		if len(x.Items) < len(items) {
			for _, item := range items[len(x.Items):] {
				p.fprintf(w, "%v", item.Equals.AllTrivia().CollapseWhitespace())
				if item.Comma != nil {
					p.fprintf(w, "%v", item.Comma.AllTrivia().CollapseWhitespace())
				}
			}
		}
	})

	if x.Tokens != nil {
		pre := ""
		if isMultiLine && !trailingNewline {
			pre = "\n" + p.indent
		}
		p.fprintf(w, "%s%v%)", pre, x.Tokens.CloseBrace, x.Tokens.Parentheses)
	} else {
		p.fprintf(w, "\n%s}", p.indent)
	}
}

func (*ObjectConsExpression) isExpression() {}

func getTraverserTrivia(tokens syntax.TraverserTokens) (syntax.TriviaList, syntax.TriviaList) {
	var leading, trailing syntax.TriviaList
	switch tokens := tokens.(type) {
	case *syntax.DotTraverserTokens:
		leading = getExprLeadingTrivia(tokens.Parentheses, tokens.Dot)
		trailing = getExprTrailingTrivia(tokens.Parentheses, tokens.Index)
	case *syntax.BracketTraverserTokens:
		leading = getExprLeadingTrivia(tokens.Parentheses, tokens.OpenBracket)
		trailing = getExprTrailingTrivia(tokens.Parentheses, tokens.CloseBracket)
	}
	return leading, trailing
}

func setTraverserTrailingTrivia(tokens syntax.TraverserTokens, t syntax.TriviaList) {
	switch tokens := tokens.(type) {
	case *syntax.DotTraverserTokens:
		setExprTrailingTrivia(tokens.Parentheses, &tokens.Index, t)
	case *syntax.BracketTraverserTokens:
		setExprTrailingTrivia(tokens.Parentheses, &tokens.CloseBracket, t)
	default:
		panic(fmt.Errorf("unexpected traverser of type %T", tokens))
	}
}

func printTraverser(w io.Writer, p *printer, t hcl.Traverser, tokens syntax.TraverserTokens) {
	var index string
	switch t := t.(type) {
	case hcl.TraverseAttr:
		index = t.Name
	case hcl.TraverseIndex:
		index = literalText(t.Key, nil, true, true)
	default:
		panic(fmt.Errorf("unexpected traverser of type %T", t))
	}

	switch tokens := tokens.(type) {
	case *syntax.DotTraverserTokens:
		p.fprintf(w, "%(%v%v%)",
			tokens.Parentheses,
			tokens.Dot, identToken(tokens.Index, index),
			tokens.Parentheses)
	case *syntax.BracketTraverserTokens:
		p.fprintf(w, "%(%v%v%v%)",
			tokens.Parentheses,
			tokens.OpenBracket, identToken(tokens.Index, index), tokens.CloseBracket,
			tokens.Parentheses)
	default:
		panic(fmt.Errorf("unexpected traverser tokens of type %T", tokens))
	}
}

func printRelativeTraversal(w io.Writer, p *printer, traversal hcl.Traversal, tokens []syntax.TraverserTokens) {
	for i, traverser := range traversal {
		// Fetch the traversal tokens.
		var traverserTokens syntax.TraverserTokens
		if i < len(tokens) {
			traverserTokens = tokens[i]
		}
		printTraverser(w, p, traverser, traverserTokens)
	}

	// Print any remaining trivia.
	if len(traversal) < len(tokens) {
		for _, tokens := range tokens[len(traversal):] {
			var trivia syntax.TriviaList
			switch tokens := tokens.(type) {
			case *syntax.DotTraverserTokens:
				trivia = tokens.Dot.LeadingTrivia
				trivia = append(trivia, tokens.Dot.TrailingTrivia...)
				trivia = append(trivia, tokens.Index.LeadingTrivia...)
				trivia = append(trivia, tokens.Index.TrailingTrivia...)
			case *syntax.BracketTraverserTokens:
				trivia = tokens.OpenBracket.LeadingTrivia
				trivia = append(trivia, tokens.OpenBracket.TrailingTrivia...)
				trivia = append(trivia, tokens.Index.LeadingTrivia...)
				trivia = append(trivia, tokens.Index.TrailingTrivia...)
				trivia = append(trivia, tokens.CloseBracket.LeadingTrivia...)
				trivia = append(trivia, tokens.CloseBracket.TrailingTrivia...)
			}
			p.fprintf(w, "%v", trivia)
		}
	}
}

// RelativeTraversalExpression represents a semantically-analyzed relative traversal expression.
type RelativeTraversalExpression struct {
	// The syntax node associated with the relative traversal expression.
	Syntax *hclsyntax.RelativeTraversalExpr
	// The tokens associated with the expression, if any.
	Tokens *syntax.RelativeTraversalTokens

	// The expression that computes the value being traversed.
	Source Expression
	// The traversal's parts.
	Parts []Traversable

	// The traversers.
	Traversal hcl.Traversal
}

// SyntaxNode returns the syntax node associated with the relative traversal expression.
func (x *RelativeTraversalExpression) SyntaxNode() hclsyntax.Node {
	if x.Syntax == nil {
		return syntax.None
	}
	return x.Syntax
}

// NodeTokens returns the tokens associated with the relative traversal expression.
func (x *RelativeTraversalExpression) NodeTokens() syntax.NodeTokens {
	return x.Tokens
}

// Type returns the type of the relative traversal expression.
func (x *RelativeTraversalExpression) Type() Type {
	return GetTraversableType(x.Parts[len(x.Parts)-1])
}

func (x *RelativeTraversalExpression) typecheck(typecheckOperands, allowMissingVariables bool) hcl.Diagnostics {
	var diagnostics hcl.Diagnostics

	if typecheckOperands {
		sourceDiags := x.Source.Typecheck(true)
		diagnostics = append(diagnostics, sourceDiags...)
	}

	parts, partDiags := bindTraversalParts(x.Source.Type(), x.Traversal, allowMissingVariables)
	diagnostics = append(diagnostics, partDiags...)

	x.Parts = parts
	return diagnostics
}

func (x *RelativeTraversalExpression) Typecheck(typecheckOperands bool) hcl.Diagnostics {
	return x.typecheck(typecheckOperands, false)
}

func (x *RelativeTraversalExpression) Evaluate(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	syntax := &hclsyntax.RelativeTraversalExpr{
		Source:    &syntaxExpr{expr: x.Source},
		Traversal: x.Traversal,
	}
	return syntax.Value(context)
}

func (x *RelativeTraversalExpression) HasLeadingTrivia() bool {
	return exprHasLeadingTrivia(x.Tokens.GetParentheses(), x.Source)
}

func (x *RelativeTraversalExpression) HasTrailingTrivia() bool {
	if parens := x.Tokens.GetParentheses(); parens.Any() {
		return true
	}
	if x.Tokens != nil && len(x.Tokens.Traversal) > 0 {
		return true
	}
	return x.Source.HasTrailingTrivia()
}

func (x *RelativeTraversalExpression) GetLeadingTrivia() syntax.TriviaList {
	return getExprLeadingTrivia(x.Tokens.GetParentheses(), x.Source)
}

func (x *RelativeTraversalExpression) SetLeadingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewRelativeTraversalTokens(x.Traversal)
	}
	setExprLeadingTrivia(x.Tokens.Parentheses, x.Source, t)
}

func (x *RelativeTraversalExpression) GetTrailingTrivia() syntax.TriviaList {
	if parens := x.Tokens.GetParentheses(); parens.Any() {
		return parens.GetTrailingTrivia()
	}
	if traversal := x.Tokens.GetTraversal(x.Traversal); len(traversal) > 0 {
		_, trailingTrivia := getTraverserTrivia(traversal[len(traversal)-1])
		return trailingTrivia
	}
	return x.Source.GetTrailingTrivia()
}

func (x *RelativeTraversalExpression) SetTrailingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewRelativeTraversalTokens(x.Traversal)
	}
	if parens := x.Tokens.GetParentheses(); parens.Any() {
		parens.SetTrailingTrivia(t)
		return
	}
	if len(x.Tokens.Traversal) > 0 {
		setTraverserTrailingTrivia(x.Tokens.Traversal[len(x.Tokens.Traversal)-1], t)
		return
	}
	x.Source.SetTrailingTrivia(t)
}

func (x *RelativeTraversalExpression) Format(f fmt.State, c rune) {
	x.print(f, &printer{})
}

func (x *RelativeTraversalExpression) print(w io.Writer, p *printer) {
	// Print the source expression.
	p.fprintf(w, "%(%v", x.Tokens.GetParentheses(), x.Source)

	// Print the traversal.
	printRelativeTraversal(w, p, x.Traversal, x.Tokens.GetTraversal(x.Traversal))

	// Print the closing parentheses, if any.
	p.fprintf(w, "%)", x.Tokens.GetParentheses())
}

func (*RelativeTraversalExpression) isExpression() {}

// ScopeTraversalExpression represents a semantically-analyzed scope traversal expression.
type ScopeTraversalExpression struct {
	// The syntax node associated with the scope traversal expression.
	Syntax *hclsyntax.ScopeTraversalExpr
	// The tokens associated with the expression, if any.
	Tokens *syntax.ScopeTraversalTokens

	// The traversal's parts.
	Parts []Traversable

	// The root name.
	RootName string
	// The traversers.
	Traversal hcl.Traversal
}

// SyntaxNode returns the syntax node associated with the scope traversal expression.
func (x *ScopeTraversalExpression) SyntaxNode() hclsyntax.Node {
	if x.Syntax == nil {
		return syntax.None
	}
	return x.Syntax
}

// NodeTokens returns the tokens associated with the scope traversal expression.
func (x *ScopeTraversalExpression) NodeTokens() syntax.NodeTokens {
	return x.Tokens
}

// Type returns the type of the scope traversal expression.
func (x *ScopeTraversalExpression) Type() Type {
	return GetTraversableType(x.Parts[len(x.Parts)-1])
}

func (x *ScopeTraversalExpression) typecheck(typecheckOperands, allowMissingVariables bool) hcl.Diagnostics {
	parts, diagnostics := bindTraversalParts(x.Parts[0], x.Traversal.SimpleSplit().Rel, allowMissingVariables)
	x.Parts = parts

	return diagnostics
}

func (x *ScopeTraversalExpression) Typecheck(typecheckOperands bool) hcl.Diagnostics {
	return x.typecheck(typecheckOperands, false)
}

func (x *ScopeTraversalExpression) Evaluate(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	var diagnostics hcl.Diagnostics

	root, hasValue := x.Parts[0].(ValueTraversable)
	if !hasValue {
		return cty.UnknownVal(cty.DynamicPseudoType), nil
	}

	rootValue, diags := root.Value(context)
	if diags.HasErrors() {
		return cty.NilVal, diags
	}
	diagnostics = append(diagnostics, diags...)

	if len(x.Traversal) == 1 {
		return rootValue, diagnostics
	}
	return x.Traversal[1:].TraverseRel(rootValue)
}

func (x *ScopeTraversalExpression) HasLeadingTrivia() bool {
	return exprHasLeadingTrivia(x.Tokens.GetParentheses(), x.Tokens != nil)
}

func (x *ScopeTraversalExpression) HasTrailingTrivia() bool {
	if parens := x.Tokens.GetParentheses(); parens.Any() {
		return true
	}
	if x.Tokens != nil && len(x.Tokens.Traversal) > 0 {
		return true
	}
	return x.Tokens != nil
}

func (x *ScopeTraversalExpression) GetLeadingTrivia() syntax.TriviaList {
	return getExprLeadingTrivia(x.Tokens.GetParentheses(), x.Tokens.GetRoot(x.Traversal))
}

func (x *ScopeTraversalExpression) SetLeadingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewScopeTraversalTokens(x.Traversal)
	}
	x.Tokens.Root.LeadingTrivia = t
}

func (x *ScopeTraversalExpression) GetTrailingTrivia() syntax.TriviaList {
	if parens := x.Tokens.GetParentheses(); parens.Any() {
		return parens.GetTrailingTrivia()
	}
	if traversal := x.Tokens.GetTraversal(x.Traversal); len(traversal) > 0 {
		_, trailingTrivia := getTraverserTrivia(traversal[len(traversal)-1])
		return trailingTrivia
	}
	return x.Tokens.GetRoot(x.Traversal).TrailingTrivia
}

func (x *ScopeTraversalExpression) SetTrailingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewScopeTraversalTokens(x.Traversal)
	}
	if parens := x.Tokens.GetParentheses(); parens.Any() {
		parens.SetTrailingTrivia(t)
		return
	}
	if len(x.Tokens.Traversal) > 0 {
		setTraverserTrailingTrivia(x.Tokens.Traversal[len(x.Tokens.Traversal)-1], t)
		return
	}
	x.Tokens.Root.TrailingTrivia = t
}

func (x *ScopeTraversalExpression) Format(f fmt.State, c rune) {
	x.print(f, &printer{})
}

func (x *ScopeTraversalExpression) print(w io.Writer, p *printer) {
	// Print the root name.
	p.fprintf(w, "%(%v", x.Tokens.GetParentheses(), x.Tokens.GetRoot(x.Traversal))

	// Print the traversal.
	printRelativeTraversal(w, p, x.Traversal[1:], x.Tokens.GetTraversal(x.Traversal))

	// Print the closing parentheses, if any.
	p.fprintf(w, "%)", x.Tokens.GetParentheses())
}

func (*ScopeTraversalExpression) isExpression() {}

type SplatVariable struct {
	Variable

	symbol hclsyntax.AnonSymbolExpr
}

func (v *SplatVariable) Value(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	return (&v.symbol).Value(context)
}

// SplatExpression represents a semantically-analyzed splat expression.
type SplatExpression struct {
	// The syntax node associated with the splat expression.
	Syntax *hclsyntax.SplatExpr
	// The tokens associated with the expression, if any.
	Tokens *syntax.SplatTokens

	// The expression being splatted.
	Source Expression
	// The expression applied to each element of the splat.
	Each Expression
	// The local variable definition associated with the current item being processed. This definition is not part of
	// a scope, and can only be referenced by an AnonSymbolExpr.
	Item *SplatVariable

	exprType Type
}

// SyntaxNode returns the syntax node associated with the splat expression.
func (x *SplatExpression) SyntaxNode() hclsyntax.Node {
	if x.Syntax == nil {
		return syntax.None
	}
	return x.Syntax
}

// NodeTokens returns the tokens associated with the splat expression.
func (x *SplatExpression) NodeTokens() syntax.NodeTokens {
	return x.Tokens
}

// Type returns the type of the splat expression.
func (x *SplatExpression) Type() Type {
	return x.exprType
}

func splatItemType(source Expression, splatSyntax *hclsyntax.SplatExpr) (Expression, Type) {
	sourceType := unwrapIterableSourceType(source.Type())
	itemType := sourceType
	switch sourceType := sourceType.(type) {
	case *ListType:
		itemType = sourceType.ElementType
	case *SetType:
		itemType = sourceType.ElementType
	case *TupleType:
		itemType, _ = UnifyTypes(sourceType.ElementTypes...)
	default:
		if sourceType != DynamicType {
			var tupleSyntax *hclsyntax.TupleConsExpr
			if splatSyntax != nil {
				tupleSyntax = &hclsyntax.TupleConsExpr{
					Exprs:     []hclsyntax.Expression{splatSyntax.Source},
					SrcRange:  splatSyntax.Source.Range(),
					OpenRange: splatSyntax.Source.StartRange(),
				}
			}

			source = &TupleConsExpression{
				Syntax:      tupleSyntax,
				Tokens:      syntax.NewTupleConsTokens(1),
				Expressions: []Expression{source},
				exprType:    NewListType(source.Type()),
			}
		}
	}
	return source, itemType
}

func (x *SplatExpression) typecheck(retypeItem, typecheckOperands bool) hcl.Diagnostics {
	var diagnostics hcl.Diagnostics

	if typecheckOperands {
		sourceDiags := x.Source.Typecheck(true)
		diagnostics = append(diagnostics, sourceDiags...)
	}

	if retypeItem {
		x.Source, x.Item.VariableType = splatItemType(x.Source, x.Syntax)
	}

	if typecheckOperands {
		eachDiags := x.Each.Typecheck(true)
		diagnostics = append(diagnostics, eachDiags...)
	}

	x.exprType = wrapIterableResultType(x.Source.Type(), NewListType(x.Each.Type()))

	return diagnostics
}

func (x *SplatExpression) Typecheck(typecheckOperands bool) hcl.Diagnostics {
	return x.typecheck(true, typecheckOperands)
}

func (x *SplatExpression) Evaluate(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	syntax := &hclsyntax.SplatExpr{
		Source: &syntaxExpr{expr: x.Source},
		Each:   &syntaxExpr{expr: x.Each},
		Item:   &x.Item.symbol,
	}
	return syntax.Value(context)
}

func (x *SplatExpression) HasLeadingTrivia() bool {
	return exprHasLeadingTrivia(x.Tokens.GetParentheses(), x.Source)
}

func (x *SplatExpression) HasTrailingTrivia() bool {
	return exprHasTrailingTrivia(x.Tokens.GetParentheses(), x.Each)
}

func (x *SplatExpression) GetLeadingTrivia() syntax.TriviaList {
	return getExprLeadingTrivia(x.Tokens.GetParentheses(), x.Source)
}

func (x *SplatExpression) SetLeadingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewSplatTokens(false)
	}
	setExprLeadingTrivia(x.Tokens.Parentheses, &x.Tokens.Open, t)
}

func (x *SplatExpression) GetTrailingTrivia() syntax.TriviaList {
	if parens := x.Tokens.GetParentheses(); parens.Any() {
		return parens.GetTrailingTrivia()
	}
	if closeTok := x.Tokens.GetClose(); closeTok != nil {
		return closeTok.TrailingTrivia
	}
	return x.Tokens.GetStar().TrailingTrivia
}

func (x *SplatExpression) SetTrailingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewSplatTokens(false)
	}
	if x.Tokens.Parentheses.Any() {
		x.Tokens.Parentheses.SetTrailingTrivia(t)
		return
	}
	if x.Tokens.Close == nil {
		x.Tokens.Star.TrailingTrivia = t
		return
	}
	x.Tokens.Close.TrailingTrivia = t
}

func (x *SplatExpression) Format(f fmt.State, c rune) {
	x.print(f, &printer{})
}

func (x *SplatExpression) print(w io.Writer, p *printer) {
	isDot := x.Tokens.GetClose() == nil

	p.fprintf(w, "%(%v%v%v", x.Tokens.GetParentheses(), x.Source, x.Tokens.GetOpen(), x.Tokens.GetStar())
	if !isDot {
		p.fprintf(w, "%v", x.Tokens.GetClose())
	}
	p.fprintf(w, "%v%)", x.Each, x.Tokens.GetParentheses())
}

func (*SplatExpression) isExpression() {}

// TemplateExpression represents a semantically-analyzed template expression.
type TemplateExpression struct {
	// The syntax node associated with the template expression.
	Syntax *hclsyntax.TemplateExpr
	// The tokens associated with the expression, if any.
	Tokens *syntax.TemplateTokens

	// The parts of the template expression.
	Parts []Expression

	exprType Type
}

// SyntaxNode returns the syntax node associated with the template expression.
func (x *TemplateExpression) SyntaxNode() hclsyntax.Node {
	if x.Syntax == nil {
		return syntax.None
	}
	return x.Syntax
}

// NodeTokens returns the tokens associated with the template expression.
func (x *TemplateExpression) NodeTokens() syntax.NodeTokens {
	return x.Tokens
}

// Type returns the type of the template expression.
func (x *TemplateExpression) Type() Type {
	return x.exprType
}

func (x *TemplateExpression) Typecheck(typecheckOperands bool) hcl.Diagnostics {
	var diagnostics hcl.Diagnostics

	if typecheckOperands {
		for _, part := range x.Parts {
			partDiags := part.Typecheck(true)
			diagnostics = append(diagnostics, partDiags...)
		}
	}

	x.exprType = liftOperationType(StringType, x.Parts...)
	return diagnostics
}

func (x *TemplateExpression) Evaluate(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	syntax := &hclsyntax.TemplateExpr{
		Parts: make([]hclsyntax.Expression, len(x.Parts)),
	}
	for i, p := range x.Parts {
		syntax.Parts[i] = &syntaxExpr{expr: p}
	}
	return syntax.Value(context)
}

func (x *TemplateExpression) HasLeadingTrivia() bool {
	return exprHasLeadingTrivia(x.Tokens.GetParentheses(), x.Tokens != nil)
}

func (x *TemplateExpression) HasTrailingTrivia() bool {
	return exprHasTrailingTrivia(x.Tokens.GetParentheses(), x.Tokens != nil)
}

func (x *TemplateExpression) GetLeadingTrivia() syntax.TriviaList {
	return getExprLeadingTrivia(x.Tokens.GetParentheses(), x.Tokens.GetOpen())
}

func (x *TemplateExpression) SetLeadingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewTemplateTokens()
	}
	setExprLeadingTrivia(x.Tokens.Parentheses, &x.Tokens.Open, t)
}

func (x *TemplateExpression) GetTrailingTrivia() syntax.TriviaList {
	return getExprTrailingTrivia(x.Tokens.GetParentheses(), x.Tokens.GetClose())
}

func (x *TemplateExpression) SetTrailingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewTemplateTokens()
	}
	setExprTrailingTrivia(x.Tokens.Parentheses, &x.Tokens.Close, t)
}

func (x *TemplateExpression) Format(f fmt.State, c rune) {
	x.print(f, &printer{})
}

func (x *TemplateExpression) print(w io.Writer, p *printer) {
	// Print the opening quote.
	p.fprintf(w, "%(%v", x.Tokens.GetParentheses(), x.Tokens.GetOpen())

	isHeredoc := x.Tokens.GetOpen().Raw.Type == hclsyntax.TokenOHeredoc

	// Print the expressions.
	for _, part := range x.Parts {
		if lit, ok := part.(*LiteralValueExpression); ok && StringType.AssignableFrom(lit.Type()) {
			lit.printLit(w, p, !isHeredoc)
		} else {
			p.fprintf(w, "%v", part)
		}
	}

	// Print the closing quote
	p.fprintf(w, "%v%)", x.Tokens.GetClose(), x.Tokens.GetParentheses())
}

func (*TemplateExpression) isExpression() {}

// TemplateJoinExpression represents a semantically-analyzed template join expression.
type TemplateJoinExpression struct {
	// The syntax node associated with the template join expression.
	Syntax *hclsyntax.TemplateJoinExpr

	// The tuple being joined.
	Tuple Expression

	exprType Type
}

// SyntaxNode returns the syntax node associated with the template join expression.
func (x *TemplateJoinExpression) SyntaxNode() hclsyntax.Node {
	if x.Syntax == nil {
		return syntax.None
	}
	return x.Syntax
}

// NodeTokens returns the tokens associated with the template join expression.
func (x *TemplateJoinExpression) NodeTokens() syntax.NodeTokens {
	return nil
}

// Type returns the type of the template join expression.
func (x *TemplateJoinExpression) Type() Type {
	return x.exprType
}

func (x *TemplateJoinExpression) Typecheck(typecheckOperands bool) hcl.Diagnostics {
	var diagnostics hcl.Diagnostics

	if typecheckOperands {
		tupleDiags := x.Tuple.Typecheck(true)
		diagnostics = append(diagnostics, tupleDiags...)
	}

	x.exprType = liftOperationType(StringType, x.Tuple)
	return diagnostics
}

func (x *TemplateJoinExpression) Evaluate(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	syntax := &hclsyntax.TemplateJoinExpr{
		Tuple: &syntaxExpr{expr: x.Tuple},
	}
	return syntax.Value(context)
}

func (x *TemplateJoinExpression) HasLeadingTrivia() bool {
	return x.Tuple.HasLeadingTrivia()
}

func (x *TemplateJoinExpression) HasTrailingTrivia() bool {
	return x.Tuple.HasTrailingTrivia()
}

func (x *TemplateJoinExpression) GetLeadingTrivia() syntax.TriviaList {
	return x.Tuple.GetLeadingTrivia()
}

func (x *TemplateJoinExpression) SetLeadingTrivia(t syntax.TriviaList) {
	x.Tuple.SetLeadingTrivia(t)
}

func (x *TemplateJoinExpression) GetTrailingTrivia() syntax.TriviaList {
	return x.Tuple.GetTrailingTrivia()
}

func (x *TemplateJoinExpression) SetTrailingTrivia(t syntax.TriviaList) {
	x.Tuple.SetTrailingTrivia(t)
}

func (x *TemplateJoinExpression) Format(f fmt.State, c rune) {
	x.print(f, &printer{})
}

func (x *TemplateJoinExpression) print(w io.Writer, p *printer) {
	p.fprintf(w, "%v", x.Tuple)
}

func (*TemplateJoinExpression) isExpression() {}

// TupleConsExpression represents a semantically-analyzed tuple construction expression.
type TupleConsExpression struct {
	// The syntax node associated with the tuple construction expression.
	Syntax *hclsyntax.TupleConsExpr
	// The tokens associated with the expression, if any.
	Tokens *syntax.TupleConsTokens

	// The elements of the tuple.
	Expressions []Expression

	exprType Type
}

// SyntaxNode returns the syntax node associated with the tuple construction expression.
func (x *TupleConsExpression) SyntaxNode() hclsyntax.Node {
	if x.Syntax == nil {
		return syntax.None
	}
	return x.Syntax
}

// NodeTokens returns the tokens associated with the tuple construction expression.
func (x *TupleConsExpression) NodeTokens() syntax.NodeTokens {
	return x.Tokens
}

// Type returns the type of the tuple construction expression.
func (x *TupleConsExpression) Type() Type {
	return x.exprType
}

func (x *TupleConsExpression) Typecheck(typecheckOperands bool) hcl.Diagnostics {
	var diagnostics hcl.Diagnostics

	elementTypes := make([]Type, len(x.Expressions))
	for i, expr := range x.Expressions {
		if typecheckOperands {
			exprDiags := expr.Typecheck(true)
			diagnostics = append(diagnostics, exprDiags...)
		}

		elementTypes[i] = expr.Type()
	}

	x.exprType = NewTupleType(elementTypes...)
	return diagnostics
}

func (x *TupleConsExpression) Evaluate(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	syntax := &hclsyntax.TupleConsExpr{
		Exprs: make([]hclsyntax.Expression, len(x.Expressions)),
	}
	for i, x := range x.Expressions {
		syntax.Exprs[i] = &syntaxExpr{expr: x}
	}
	return syntax.Value(context)
}

func (x *TupleConsExpression) HasLeadingTrivia() bool {
	return exprHasLeadingTrivia(x.Tokens.GetParentheses(), x.Tokens != nil)
}

func (x *TupleConsExpression) HasTrailingTrivia() bool {
	return exprHasTrailingTrivia(x.Tokens.GetParentheses(), x.Tokens != nil)
}

func (x *TupleConsExpression) GetLeadingTrivia() syntax.TriviaList {
	return getExprLeadingTrivia(x.Tokens.GetParentheses(), x.Tokens.GetOpenBracket())
}

func (x *TupleConsExpression) SetLeadingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewTupleConsTokens(len(x.Expressions))
	}
	setExprLeadingTrivia(x.Tokens.Parentheses, &x.Tokens.OpenBracket, t)
}

func (x *TupleConsExpression) GetTrailingTrivia() syntax.TriviaList {
	return getExprTrailingTrivia(x.Tokens.GetParentheses(), x.Tokens.GetCloseBracket())
}

func (x *TupleConsExpression) SetTrailingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewTupleConsTokens(len(x.Expressions))
	}
	setExprTrailingTrivia(x.Tokens.Parentheses, &x.Tokens.CloseBracket, t)
}

func (x *TupleConsExpression) Format(f fmt.State, c rune) {
	x.print(f, &printer{})
}

func (x *TupleConsExpression) print(w io.Writer, p *printer) {
	// Print the opening bracket.
	p.fprintf(w, "%(%v", x.Tokens.GetParentheses(), x.Tokens.GetOpenBracket())

	// Print each element and its comma.
	commas := x.Tokens.GetCommas(len(x.Expressions))
	p.indented(func() {
		for i, expr := range x.Expressions {
			if !expr.HasLeadingTrivia() {
				p.fprintf(w, "\n%s", p.indent)
			}
			p.fprintf(w, "%v", expr)

			if i != len(x.Expressions)-1 {
				var comma syntax.Token
				if i < len(commas) {
					comma = commas[i]
				}
				p.fprintf(w, "%v", comma)
			}
		}

		// If there were commas left over, print the trivia for each.
		//
		// TODO(pdg): filter to only comments?
		if len(x.Expressions) > 0 && len(x.Expressions)-1 <= len(commas) {
			for _, comma := range commas[len(x.Expressions)-1:] {
				p.fprintf(w, "%v", comma.AllTrivia().CollapseWhitespace())
			}
		}
	})

	// Print the closing bracket.
	if x.Tokens != nil {
		p.fprintf(w, "%v%)", x.Tokens.CloseBracket, x.Tokens.GetParentheses())
	} else {
		p.fprintf(w, "\n%s]", p.indent)
	}
}

func (*TupleConsExpression) isExpression() {}

// UnaryOpExpression represents a semantically-analyzed unary operation.
type UnaryOpExpression struct {
	// The syntax node associated with the unary operation.
	Syntax *hclsyntax.UnaryOpExpr
	// The tokens associated with the expression, if any.
	Tokens *syntax.UnaryOpTokens

	// The operation.
	Operation *hclsyntax.Operation
	// The operand of the operation.
	Operand Expression

	operandType Type
	exprType    Type
}

// SyntaxNode returns the syntax node associated with the unary operation.
func (x *UnaryOpExpression) SyntaxNode() hclsyntax.Node {
	if x.Syntax == nil {
		return syntax.None
	}
	return x.Syntax
}

// NodeTokens returns the tokens associated with the unary operation.
func (x *UnaryOpExpression) NodeTokens() syntax.NodeTokens {
	return x.Tokens
}

// OperandType returns the operand type of the unary operation.
func (x *UnaryOpExpression) OperandType() Type {
	return x.operandType
}

// Type returns the type of the unary operation.
func (x *UnaryOpExpression) Type() Type {
	return x.exprType
}

func (x *UnaryOpExpression) Typecheck(typecheckOperands bool) hcl.Diagnostics {
	var diagnostics hcl.Diagnostics

	if typecheckOperands {
		operandDiags := x.Operand.Typecheck(true)
		diagnostics = append(diagnostics, operandDiags...)
	}

	// Compute the signature for the operator and typecheck the arguments.
	signature := getOperationSignature(x.Operation)
	contract.Assertf(len(signature.Parameters) == 1,
		"expected unary operator signature to have 1 parameter, got %d", len(signature.Parameters))

	x.operandType = signature.Parameters[0].Type

	var rng hcl.Range
	if x.Syntax != nil {
		rng = x.Syntax.Range()
	}
	typecheckDiags := typecheckArgs(rng, signature, x.Operand)
	diagnostics = append(diagnostics, typecheckDiags...)

	x.exprType = liftOperationType(signature.ReturnType, x.Operand)
	return diagnostics
}

func (x *UnaryOpExpression) Evaluate(context *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
	syntax := &hclsyntax.UnaryOpExpr{
		Op:  x.Operation,
		Val: &syntaxExpr{expr: x.Operand},
	}
	return syntax.Value(context)
}

func (x *UnaryOpExpression) HasLeadingTrivia() bool {
	return exprHasLeadingTrivia(x.Tokens.GetParentheses(), x.Tokens != nil)
}

func (x *UnaryOpExpression) HasTrailingTrivia() bool {
	return exprHasTrailingTrivia(x.Tokens.GetParentheses(), x.Operand)
}

func (x *UnaryOpExpression) GetLeadingTrivia() syntax.TriviaList {
	return getExprLeadingTrivia(x.Tokens.GetParentheses(), x.Tokens.GetOperator(x.Operation))
}

func (x *UnaryOpExpression) SetLeadingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewUnaryOpTokens(x.Operation)
	}
	setExprLeadingTrivia(x.Tokens.Parentheses, &x.Tokens.Operator, t)
}

func (x *UnaryOpExpression) GetTrailingTrivia() syntax.TriviaList {
	return getExprTrailingTrivia(x.Tokens.GetParentheses(), x.Operand)
}

func (x *UnaryOpExpression) SetTrailingTrivia(t syntax.TriviaList) {
	if x.Tokens == nil {
		x.Tokens = syntax.NewUnaryOpTokens(x.Operation)
	}
	setExprTrailingTrivia(x.Tokens.Parentheses, x.Operand, t)
}

func (x *UnaryOpExpression) Format(f fmt.State, c rune) {
	x.print(f, &printer{})
}

func (x *UnaryOpExpression) print(w io.Writer, p *printer) {
	precedence := operatorPrecedence(x.Operation)
	p.fprintf(w, "%[2](%[3]v%.[1]*[4]v%[5])",
		precedence,
		x.Tokens.GetParentheses(),
		x.Tokens.GetOperator(x.Operation), x.Operand,
		x.Tokens.GetParentheses())
}

func (*UnaryOpExpression) isExpression() {}