mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-09-19 09:39:59 +00:00
669 lines
16 KiB
Go
669 lines
16 KiB
Go
|
package hil
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"sync"
|
||
|
|
||
|
"github.com/hashicorp/hil/ast"
|
||
|
)
|
||
|
|
||
|
// TypeCheck implements ast.Visitor for type checking an AST tree.
|
||
|
// It requires some configuration to look up the type of nodes.
|
||
|
//
|
||
|
// It also optionally will not type error and will insert an implicit
|
||
|
// type conversions for specific types if specified by the Implicit
|
||
|
// field. Note that this is kind of organizationally weird to put into
|
||
|
// this structure but we'd rather do that than duplicate the type checking
|
||
|
// logic multiple times.
|
||
|
type TypeCheck struct {
|
||
|
Scope ast.Scope
|
||
|
|
||
|
// Implicit is a map of implicit type conversions that we can do,
|
||
|
// and that shouldn't error. The key of the first map is the from type,
|
||
|
// the key of the second map is the to type, and the final string
|
||
|
// value is the function to call (which must be registered in the Scope).
|
||
|
Implicit map[ast.Type]map[ast.Type]string
|
||
|
|
||
|
// Stack of types. This shouldn't be used directly except by implementations
|
||
|
// of TypeCheckNode.
|
||
|
Stack []ast.Type
|
||
|
|
||
|
err error
|
||
|
lock sync.Mutex
|
||
|
}
|
||
|
|
||
|
// TypeCheckNode is the interface that must be implemented by any
|
||
|
// ast.Node that wants to support type-checking. If the type checker
|
||
|
// encounters a node that doesn't implement this, it will error.
|
||
|
type TypeCheckNode interface {
|
||
|
TypeCheck(*TypeCheck) (ast.Node, error)
|
||
|
}
|
||
|
|
||
|
func (v *TypeCheck) Visit(root ast.Node) error {
|
||
|
v.lock.Lock()
|
||
|
defer v.lock.Unlock()
|
||
|
defer v.reset()
|
||
|
root.Accept(v.visit)
|
||
|
|
||
|
// If the resulting type is unknown, then just let the whole thing go.
|
||
|
if v.err == errExitUnknown {
|
||
|
v.err = nil
|
||
|
}
|
||
|
|
||
|
return v.err
|
||
|
}
|
||
|
|
||
|
func (v *TypeCheck) visit(raw ast.Node) ast.Node {
|
||
|
if v.err != nil {
|
||
|
return raw
|
||
|
}
|
||
|
|
||
|
var result ast.Node
|
||
|
var err error
|
||
|
switch n := raw.(type) {
|
||
|
case *ast.Arithmetic:
|
||
|
tc := &typeCheckArithmetic{n}
|
||
|
result, err = tc.TypeCheck(v)
|
||
|
case *ast.Call:
|
||
|
tc := &typeCheckCall{n}
|
||
|
result, err = tc.TypeCheck(v)
|
||
|
case *ast.Conditional:
|
||
|
tc := &typeCheckConditional{n}
|
||
|
result, err = tc.TypeCheck(v)
|
||
|
case *ast.Index:
|
||
|
tc := &typeCheckIndex{n}
|
||
|
result, err = tc.TypeCheck(v)
|
||
|
case *ast.Output:
|
||
|
tc := &typeCheckOutput{n}
|
||
|
result, err = tc.TypeCheck(v)
|
||
|
case *ast.LiteralNode:
|
||
|
tc := &typeCheckLiteral{n}
|
||
|
result, err = tc.TypeCheck(v)
|
||
|
case *ast.VariableAccess:
|
||
|
tc := &typeCheckVariableAccess{n}
|
||
|
result, err = tc.TypeCheck(v)
|
||
|
default:
|
||
|
tc, ok := raw.(TypeCheckNode)
|
||
|
if !ok {
|
||
|
err = fmt.Errorf("unknown node for type check: %#v", raw)
|
||
|
break
|
||
|
}
|
||
|
|
||
|
result, err = tc.TypeCheck(v)
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
pos := raw.Pos()
|
||
|
v.err = fmt.Errorf("At column %d, line %d: %s",
|
||
|
pos.Column, pos.Line, err)
|
||
|
}
|
||
|
|
||
|
return result
|
||
|
}
|
||
|
|
||
|
type typeCheckArithmetic struct {
|
||
|
n *ast.Arithmetic
|
||
|
}
|
||
|
|
||
|
func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
||
|
// The arguments are on the stack in reverse order, so pop them off.
|
||
|
exprs := make([]ast.Type, len(tc.n.Exprs))
|
||
|
for i, _ := range tc.n.Exprs {
|
||
|
exprs[len(tc.n.Exprs)-1-i] = v.StackPop()
|
||
|
}
|
||
|
|
||
|
// If any operand is unknown then our result is automatically unknown
|
||
|
for _, ty := range exprs {
|
||
|
if ty == ast.TypeUnknown {
|
||
|
v.StackPush(ast.TypeUnknown)
|
||
|
return tc.n, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch tc.n.Op {
|
||
|
case ast.ArithmeticOpLogicalAnd, ast.ArithmeticOpLogicalOr:
|
||
|
return tc.checkLogical(v, exprs)
|
||
|
case ast.ArithmeticOpEqual, ast.ArithmeticOpNotEqual,
|
||
|
ast.ArithmeticOpLessThan, ast.ArithmeticOpGreaterThan,
|
||
|
ast.ArithmeticOpGreaterThanOrEqual, ast.ArithmeticOpLessThanOrEqual:
|
||
|
return tc.checkComparison(v, exprs)
|
||
|
default:
|
||
|
return tc.checkNumeric(v, exprs)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
func (tc *typeCheckArithmetic) checkNumeric(v *TypeCheck, exprs []ast.Type) (ast.Node, error) {
|
||
|
// Determine the resulting type we want. We do this by going over
|
||
|
// every expression until we find one with a type we recognize.
|
||
|
// We do this because the first expr might be a string ("var.foo")
|
||
|
// and we need to know what to implicit to.
|
||
|
mathFunc := "__builtin_IntMath"
|
||
|
mathType := ast.TypeInt
|
||
|
for _, v := range exprs {
|
||
|
// We assume int math but if we find ANY float, the entire
|
||
|
// expression turns into floating point math.
|
||
|
if v == ast.TypeFloat {
|
||
|
mathFunc = "__builtin_FloatMath"
|
||
|
mathType = v
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Verify the args
|
||
|
for i, arg := range exprs {
|
||
|
if arg != mathType {
|
||
|
cn := v.ImplicitConversion(exprs[i], mathType, tc.n.Exprs[i])
|
||
|
if cn != nil {
|
||
|
tc.n.Exprs[i] = cn
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
return nil, fmt.Errorf(
|
||
|
"operand %d should be %s, got %s",
|
||
|
i+1, mathType, arg)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Modulo doesn't work for floats
|
||
|
if mathType == ast.TypeFloat && tc.n.Op == ast.ArithmeticOpMod {
|
||
|
return nil, fmt.Errorf("modulo cannot be used with floats")
|
||
|
}
|
||
|
|
||
|
// Return type
|
||
|
v.StackPush(mathType)
|
||
|
|
||
|
// Replace our node with a call to the proper function. This isn't
|
||
|
// type checked but we already verified types.
|
||
|
args := make([]ast.Node, len(tc.n.Exprs)+1)
|
||
|
args[0] = &ast.LiteralNode{
|
||
|
Value: tc.n.Op,
|
||
|
Typex: ast.TypeInt,
|
||
|
Posx: tc.n.Pos(),
|
||
|
}
|
||
|
copy(args[1:], tc.n.Exprs)
|
||
|
return &ast.Call{
|
||
|
Func: mathFunc,
|
||
|
Args: args,
|
||
|
Posx: tc.n.Pos(),
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (tc *typeCheckArithmetic) checkComparison(v *TypeCheck, exprs []ast.Type) (ast.Node, error) {
|
||
|
if len(exprs) != 2 {
|
||
|
// This should never happen, because the parser never produces
|
||
|
// nodes that violate this.
|
||
|
return nil, fmt.Errorf(
|
||
|
"comparison operators must have exactly two operands",
|
||
|
)
|
||
|
}
|
||
|
|
||
|
// The first operand always dictates the type for a comparison.
|
||
|
compareFunc := ""
|
||
|
compareType := exprs[0]
|
||
|
switch compareType {
|
||
|
case ast.TypeBool:
|
||
|
compareFunc = "__builtin_BoolCompare"
|
||
|
case ast.TypeFloat:
|
||
|
compareFunc = "__builtin_FloatCompare"
|
||
|
case ast.TypeInt:
|
||
|
compareFunc = "__builtin_IntCompare"
|
||
|
case ast.TypeString:
|
||
|
compareFunc = "__builtin_StringCompare"
|
||
|
default:
|
||
|
return nil, fmt.Errorf(
|
||
|
"comparison operators apply only to bool, float, int, and string",
|
||
|
)
|
||
|
}
|
||
|
|
||
|
// For non-equality comparisons, we will do implicit conversions to
|
||
|
// integer types if possible. In this case, we need to go through and
|
||
|
// determine the type of comparison we're doing to enable the implicit
|
||
|
// conversion.
|
||
|
if tc.n.Op != ast.ArithmeticOpEqual && tc.n.Op != ast.ArithmeticOpNotEqual {
|
||
|
compareFunc = "__builtin_IntCompare"
|
||
|
compareType = ast.TypeInt
|
||
|
for _, expr := range exprs {
|
||
|
if expr == ast.TypeFloat {
|
||
|
compareFunc = "__builtin_FloatCompare"
|
||
|
compareType = ast.TypeFloat
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Verify (and possibly, convert) the args
|
||
|
for i, arg := range exprs {
|
||
|
if arg != compareType {
|
||
|
cn := v.ImplicitConversion(exprs[i], compareType, tc.n.Exprs[i])
|
||
|
if cn != nil {
|
||
|
tc.n.Exprs[i] = cn
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
return nil, fmt.Errorf(
|
||
|
"operand %d should be %s, got %s",
|
||
|
i+1, compareType, arg,
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Only ints and floats can have the <, >, <= and >= operators applied
|
||
|
switch tc.n.Op {
|
||
|
case ast.ArithmeticOpEqual, ast.ArithmeticOpNotEqual:
|
||
|
// anything goes
|
||
|
default:
|
||
|
switch compareType {
|
||
|
case ast.TypeFloat, ast.TypeInt:
|
||
|
// fine
|
||
|
default:
|
||
|
return nil, fmt.Errorf(
|
||
|
"<, >, <= and >= may apply only to int and float values",
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Comparison operators always return bool
|
||
|
v.StackPush(ast.TypeBool)
|
||
|
|
||
|
// Replace our node with a call to the proper function. This isn't
|
||
|
// type checked but we already verified types.
|
||
|
args := make([]ast.Node, len(tc.n.Exprs)+1)
|
||
|
args[0] = &ast.LiteralNode{
|
||
|
Value: tc.n.Op,
|
||
|
Typex: ast.TypeInt,
|
||
|
Posx: tc.n.Pos(),
|
||
|
}
|
||
|
copy(args[1:], tc.n.Exprs)
|
||
|
return &ast.Call{
|
||
|
Func: compareFunc,
|
||
|
Args: args,
|
||
|
Posx: tc.n.Pos(),
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (tc *typeCheckArithmetic) checkLogical(v *TypeCheck, exprs []ast.Type) (ast.Node, error) {
|
||
|
for i, t := range exprs {
|
||
|
if t != ast.TypeBool {
|
||
|
cn := v.ImplicitConversion(t, ast.TypeBool, tc.n.Exprs[i])
|
||
|
if cn == nil {
|
||
|
return nil, fmt.Errorf(
|
||
|
"logical operators require boolean operands, not %s",
|
||
|
t,
|
||
|
)
|
||
|
}
|
||
|
tc.n.Exprs[i] = cn
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Return type is always boolean
|
||
|
v.StackPush(ast.TypeBool)
|
||
|
|
||
|
// Arithmetic nodes are replaced with a call to a built-in function
|
||
|
args := make([]ast.Node, len(tc.n.Exprs)+1)
|
||
|
args[0] = &ast.LiteralNode{
|
||
|
Value: tc.n.Op,
|
||
|
Typex: ast.TypeInt,
|
||
|
Posx: tc.n.Pos(),
|
||
|
}
|
||
|
copy(args[1:], tc.n.Exprs)
|
||
|
return &ast.Call{
|
||
|
Func: "__builtin_Logical",
|
||
|
Args: args,
|
||
|
Posx: tc.n.Pos(),
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
type typeCheckCall struct {
|
||
|
n *ast.Call
|
||
|
}
|
||
|
|
||
|
func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
||
|
// Look up the function in the map
|
||
|
function, ok := v.Scope.LookupFunc(tc.n.Func)
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("unknown function called: %s", tc.n.Func)
|
||
|
}
|
||
|
|
||
|
// The arguments are on the stack in reverse order, so pop them off.
|
||
|
args := make([]ast.Type, len(tc.n.Args))
|
||
|
for i, _ := range tc.n.Args {
|
||
|
args[len(tc.n.Args)-1-i] = v.StackPop()
|
||
|
}
|
||
|
|
||
|
// Verify the args
|
||
|
for i, expected := range function.ArgTypes {
|
||
|
if expected == ast.TypeAny {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if args[i] == ast.TypeUnknown {
|
||
|
v.StackPush(ast.TypeUnknown)
|
||
|
return tc.n, nil
|
||
|
}
|
||
|
|
||
|
if args[i] != expected {
|
||
|
cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i])
|
||
|
if cn != nil {
|
||
|
tc.n.Args[i] = cn
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
return nil, fmt.Errorf(
|
||
|
"%s: argument %d should be %s, got %s",
|
||
|
tc.n.Func, i+1, expected.Printable(), args[i].Printable())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we're variadic, then verify the types there
|
||
|
if function.Variadic && function.VariadicType != ast.TypeAny {
|
||
|
args = args[len(function.ArgTypes):]
|
||
|
for i, t := range args {
|
||
|
if t == ast.TypeUnknown {
|
||
|
v.StackPush(ast.TypeUnknown)
|
||
|
return tc.n, nil
|
||
|
}
|
||
|
|
||
|
if t != function.VariadicType {
|
||
|
realI := i + len(function.ArgTypes)
|
||
|
cn := v.ImplicitConversion(
|
||
|
t, function.VariadicType, tc.n.Args[realI])
|
||
|
if cn != nil {
|
||
|
tc.n.Args[realI] = cn
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
return nil, fmt.Errorf(
|
||
|
"%s: argument %d should be %s, got %s",
|
||
|
tc.n.Func, realI,
|
||
|
function.VariadicType.Printable(), t.Printable())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Return type
|
||
|
v.StackPush(function.ReturnType)
|
||
|
|
||
|
return tc.n, nil
|
||
|
}
|
||
|
|
||
|
type typeCheckConditional struct {
|
||
|
n *ast.Conditional
|
||
|
}
|
||
|
|
||
|
func (tc *typeCheckConditional) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
||
|
// On the stack we have the types of the condition, true and false
|
||
|
// expressions, but they are in reverse order.
|
||
|
falseType := v.StackPop()
|
||
|
trueType := v.StackPop()
|
||
|
condType := v.StackPop()
|
||
|
|
||
|
if condType == ast.TypeUnknown {
|
||
|
v.StackPush(ast.TypeUnknown)
|
||
|
return tc.n, nil
|
||
|
}
|
||
|
|
||
|
if condType != ast.TypeBool {
|
||
|
cn := v.ImplicitConversion(condType, ast.TypeBool, tc.n.CondExpr)
|
||
|
if cn == nil {
|
||
|
return nil, fmt.Errorf(
|
||
|
"condition must be type bool, not %s", condType.Printable(),
|
||
|
)
|
||
|
}
|
||
|
tc.n.CondExpr = cn
|
||
|
}
|
||
|
|
||
|
// The types of the true and false expression must match
|
||
|
if trueType != falseType && trueType != ast.TypeUnknown && falseType != ast.TypeUnknown {
|
||
|
|
||
|
// Since passing around stringified versions of other types is
|
||
|
// common, we pragmatically allow the false expression to dictate
|
||
|
// the result type when the true expression is a string.
|
||
|
if trueType == ast.TypeString {
|
||
|
cn := v.ImplicitConversion(trueType, falseType, tc.n.TrueExpr)
|
||
|
if cn == nil {
|
||
|
return nil, fmt.Errorf(
|
||
|
"true and false expression types must match; have %s and %s",
|
||
|
trueType.Printable(), falseType.Printable(),
|
||
|
)
|
||
|
}
|
||
|
tc.n.TrueExpr = cn
|
||
|
trueType = falseType
|
||
|
} else {
|
||
|
cn := v.ImplicitConversion(falseType, trueType, tc.n.FalseExpr)
|
||
|
if cn == nil {
|
||
|
return nil, fmt.Errorf(
|
||
|
"true and false expression types must match; have %s and %s",
|
||
|
trueType.Printable(), falseType.Printable(),
|
||
|
)
|
||
|
}
|
||
|
tc.n.FalseExpr = cn
|
||
|
falseType = trueType
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Currently list and map types cannot be used, because we cannot
|
||
|
// generally assert that their element types are consistent.
|
||
|
// Such support might be added later, either by improving the type
|
||
|
// system or restricting usage to only variable and literal expressions,
|
||
|
// but for now this is simply prohibited because it doesn't seem to
|
||
|
// be a common enough case to be worth the complexity.
|
||
|
switch trueType {
|
||
|
case ast.TypeList:
|
||
|
return nil, fmt.Errorf(
|
||
|
"conditional operator cannot be used with list values",
|
||
|
)
|
||
|
case ast.TypeMap:
|
||
|
return nil, fmt.Errorf(
|
||
|
"conditional operator cannot be used with map values",
|
||
|
)
|
||
|
}
|
||
|
|
||
|
// Result type (guaranteed to also match falseType due to the above)
|
||
|
if trueType == ast.TypeUnknown {
|
||
|
// falseType may also be unknown, but that's okay because two
|
||
|
// unknowns means our result is unknown anyway.
|
||
|
v.StackPush(falseType)
|
||
|
} else {
|
||
|
v.StackPush(trueType)
|
||
|
}
|
||
|
|
||
|
return tc.n, nil
|
||
|
}
|
||
|
|
||
|
type typeCheckOutput struct {
|
||
|
n *ast.Output
|
||
|
}
|
||
|
|
||
|
func (tc *typeCheckOutput) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
||
|
n := tc.n
|
||
|
types := make([]ast.Type, len(n.Exprs))
|
||
|
for i, _ := range n.Exprs {
|
||
|
types[len(n.Exprs)-1-i] = v.StackPop()
|
||
|
}
|
||
|
|
||
|
for _, ty := range types {
|
||
|
if ty == ast.TypeUnknown {
|
||
|
v.StackPush(ast.TypeUnknown)
|
||
|
return tc.n, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If there is only one argument and it is a list, we evaluate to a list
|
||
|
if len(types) == 1 {
|
||
|
switch t := types[0]; t {
|
||
|
case ast.TypeList:
|
||
|
fallthrough
|
||
|
case ast.TypeMap:
|
||
|
v.StackPush(t)
|
||
|
return n, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Otherwise, all concat args must be strings, so validate that
|
||
|
resultType := ast.TypeString
|
||
|
for i, t := range types {
|
||
|
|
||
|
if t == ast.TypeUnknown {
|
||
|
resultType = ast.TypeUnknown
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if t != ast.TypeString {
|
||
|
cn := v.ImplicitConversion(t, ast.TypeString, n.Exprs[i])
|
||
|
if cn != nil {
|
||
|
n.Exprs[i] = cn
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
return nil, fmt.Errorf(
|
||
|
"output of an HIL expression must be a string, or a single list (argument %d is %s)", i+1, t)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This always results in type string, unless there are unknowns
|
||
|
v.StackPush(resultType)
|
||
|
|
||
|
return n, nil
|
||
|
}
|
||
|
|
||
|
type typeCheckLiteral struct {
|
||
|
n *ast.LiteralNode
|
||
|
}
|
||
|
|
||
|
func (tc *typeCheckLiteral) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
||
|
v.StackPush(tc.n.Typex)
|
||
|
return tc.n, nil
|
||
|
}
|
||
|
|
||
|
type typeCheckVariableAccess struct {
|
||
|
n *ast.VariableAccess
|
||
|
}
|
||
|
|
||
|
func (tc *typeCheckVariableAccess) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
||
|
// Look up the variable in the map
|
||
|
variable, ok := v.Scope.LookupVar(tc.n.Name)
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf(
|
||
|
"unknown variable accessed: %s", tc.n.Name)
|
||
|
}
|
||
|
|
||
|
// Add the type to the stack
|
||
|
v.StackPush(variable.Type)
|
||
|
|
||
|
return tc.n, nil
|
||
|
}
|
||
|
|
||
|
type typeCheckIndex struct {
|
||
|
n *ast.Index
|
||
|
}
|
||
|
|
||
|
func (tc *typeCheckIndex) TypeCheck(v *TypeCheck) (ast.Node, error) {
|
||
|
keyType := v.StackPop()
|
||
|
targetType := v.StackPop()
|
||
|
|
||
|
if keyType == ast.TypeUnknown || targetType == ast.TypeUnknown {
|
||
|
v.StackPush(ast.TypeUnknown)
|
||
|
return tc.n, nil
|
||
|
}
|
||
|
|
||
|
// Ensure we have a VariableAccess as the target
|
||
|
varAccessNode, ok := tc.n.Target.(*ast.VariableAccess)
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf(
|
||
|
"target of an index must be a VariableAccess node, was %T", tc.n.Target)
|
||
|
}
|
||
|
|
||
|
// Get the variable
|
||
|
variable, ok := v.Scope.LookupVar(varAccessNode.Name)
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf(
|
||
|
"unknown variable accessed: %s", varAccessNode.Name)
|
||
|
}
|
||
|
|
||
|
switch targetType {
|
||
|
case ast.TypeList:
|
||
|
if keyType != ast.TypeInt {
|
||
|
tc.n.Key = v.ImplicitConversion(keyType, ast.TypeInt, tc.n.Key)
|
||
|
if tc.n.Key == nil {
|
||
|
return nil, fmt.Errorf(
|
||
|
"key of an index must be an int, was %s", keyType)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
valType, err := ast.VariableListElementTypesAreHomogenous(
|
||
|
varAccessNode.Name, variable.Value.([]ast.Variable))
|
||
|
if err != nil {
|
||
|
return tc.n, err
|
||
|
}
|
||
|
|
||
|
v.StackPush(valType)
|
||
|
return tc.n, nil
|
||
|
case ast.TypeMap:
|
||
|
if keyType != ast.TypeString {
|
||
|
tc.n.Key = v.ImplicitConversion(keyType, ast.TypeString, tc.n.Key)
|
||
|
if tc.n.Key == nil {
|
||
|
return nil, fmt.Errorf(
|
||
|
"key of an index must be a string, was %s", keyType)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
valType, err := ast.VariableMapValueTypesAreHomogenous(
|
||
|
varAccessNode.Name, variable.Value.(map[string]ast.Variable))
|
||
|
if err != nil {
|
||
|
return tc.n, err
|
||
|
}
|
||
|
|
||
|
v.StackPush(valType)
|
||
|
return tc.n, nil
|
||
|
default:
|
||
|
return nil, fmt.Errorf("invalid index operation into non-indexable type: %s", variable.Type)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (v *TypeCheck) ImplicitConversion(
|
||
|
actual ast.Type, expected ast.Type, n ast.Node) ast.Node {
|
||
|
if v.Implicit == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
fromMap, ok := v.Implicit[actual]
|
||
|
if !ok {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
toFunc, ok := fromMap[expected]
|
||
|
if !ok {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return &ast.Call{
|
||
|
Func: toFunc,
|
||
|
Args: []ast.Node{n},
|
||
|
Posx: n.Pos(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (v *TypeCheck) reset() {
|
||
|
v.Stack = nil
|
||
|
v.err = nil
|
||
|
}
|
||
|
|
||
|
func (v *TypeCheck) StackPush(t ast.Type) {
|
||
|
v.Stack = append(v.Stack, t)
|
||
|
}
|
||
|
|
||
|
func (v *TypeCheck) StackPop() ast.Type {
|
||
|
var x ast.Type
|
||
|
x, v.Stack = v.Stack[len(v.Stack)-1], v.Stack[:len(v.Stack)-1]
|
||
|
return x
|
||
|
}
|
||
|
|
||
|
func (v *TypeCheck) StackPeek() ast.Type {
|
||
|
if len(v.Stack) == 0 {
|
||
|
return ast.TypeInvalid
|
||
|
}
|
||
|
|
||
|
return v.Stack[len(v.Stack)-1]
|
||
|
}
|