configs/configupgrade: Fix up internal HIL conversion functions

HIL implemented its type conversions by rewriting its AST to include calls
to some undocumented builtin functions. Unfortunately those functions were
still explicitly callable if you could figure out the name for them, and
so they may have been used in the wild.

In particular, __builtin_StringToFloat was used as part of a workaround
for a HIL design flaw where it would prefer to convert strings to integers
rather than floats when performing arithmetic operations. This issue was,
indeed, the main reason for unifying int ant float into a single number
type in HCL. Since we published that as a suggested workaround, the
upgrade tool ought to fix it up.

The other cases have never been documented as a workaround, so they are
less likely to appear in the wild, but we might as well fix them up anyway
since we already have the conversion functions required to get the same
result in the new language.

To be safe/conservative, most of these convert to _two_ function calls
rather than just one, which ensures that these new expressions retain the
behavior of implicitly converting to the source type before running the
conversion. The new conversion functions only specify target type, and so
cannot guarantee identical results if the argument type does not exactly
match what was previously given as the parameter type in HIL.
This commit is contained in:
Martin Atkins 2019-02-20 12:53:12 -08:00
parent d7641c0816
commit 1d35233a03
3 changed files with 64 additions and 1 deletions

View File

@ -13,4 +13,14 @@ locals {
lookup_literal = "${lookup(map("a", "b"), "a")}" lookup_literal = "${lookup(map("a", "b"), "a")}"
lookup_ref = "${lookup(local.map, "a")}" lookup_ref = "${lookup(local.map, "a")}"
# Undocumented HIL implementation details that some users nonetheless relied on.
conv_bool_to_string = "${__builtin_BoolToString(true)}"
conv_float_to_int = "${__builtin_FloatToInt(1.5)}"
conv_float_to_string = "${__builtin_FloatToString(1.5)}"
conv_int_to_float = "${__builtin_IntToFloat(1)}"
conv_int_to_string = "${__builtin_IntToString(1)}"
conv_string_to_int = "${__builtin_StringToInt("1")}"
conv_string_to_float = "${__builtin_StringToFloat("1.5")}"
conv_string_to_bool = "${__builtin_StringToBool("true")}"
} }

View File

@ -31,4 +31,14 @@ locals {
"a" = "b" "a" = "b"
}["a"] }["a"]
lookup_ref = local.map["a"] lookup_ref = local.map["a"]
# Undocumented HIL implementation details that some users nonetheless relied on.
conv_bool_to_string = tostring(tobool(true))
conv_float_to_int = floor(1.5)
conv_float_to_string = tostring(tonumber(1.5))
conv_int_to_float = floor(1)
conv_int_to_string = tostring(floor(1))
conv_string_to_int = floor(tostring("1"))
conv_string_to_float = tonumber(tostring("1.5"))
conv_string_to_bool = tobool(tostring("true"))
} }

View File

@ -207,7 +207,7 @@ Value:
case int: case int:
buf.WriteString(strconv.Itoa(tl)) buf.WriteString(strconv.Itoa(tl))
case float64: case float64:
buf.WriteString(strconv.FormatFloat(tl, 'f', 64, 64)) buf.WriteString(strconv.FormatFloat(tl, 'f', -1, 64))
case bool: case bool:
if tl { if tl {
buf.WriteString("true") buf.WriteString("true")
@ -398,6 +398,49 @@ Value:
buf.WriteByte(']') buf.WriteByte(']')
break Value break Value
} }
// HIL used some undocumented special functions to implement certain
// operations, but since those were actually callable in real expressions
// some users inevitably depended on them, so we'll fix them up here.
// These each become two function calls to preserve the old behavior
// of implicitly converting to the source type first. Usage of these
// is relatively rare, so the result doesn't need to be too pretty.
case "__builtin_BoolToString":
buf.WriteString("tostring(tobool(")
buf.Write(argExprs[0])
buf.WriteString("))")
break Value
case "__builtin_FloatToString":
buf.WriteString("tostring(tonumber(")
buf.Write(argExprs[0])
buf.WriteString("))")
break Value
case "__builtin_IntToString":
buf.WriteString("tostring(floor(")
buf.Write(argExprs[0])
buf.WriteString("))")
break Value
case "__builtin_StringToInt":
buf.WriteString("floor(tostring(")
buf.Write(argExprs[0])
buf.WriteString("))")
break Value
case "__builtin_StringToFloat":
buf.WriteString("tonumber(tostring(")
buf.Write(argExprs[0])
buf.WriteString("))")
break Value
case "__builtin_StringToBool":
buf.WriteString("tobool(tostring(")
buf.Write(argExprs[0])
buf.WriteString("))")
break Value
case "__builtin_FloatToInt", "__builtin_IntToFloat":
// Since "floor" already has an implicit conversion of its argument
// to number, and the result is a whole number in either case,
// these ones are easier. (We no longer distinguish int and float
// as types in HCL2, even though HIL did.)
name = "floor"
} }
buf.WriteString(name) buf.WriteString(name)