lang/funcs: Non-null refinements for various functions

cty's new "refinements" concept allows us to reduce the range of unknown
values from our functions. This initial changeset focuses only on
declaring which functions are guaranteed to return a non-null result,
which is a helpful baseline refinement because it allows "== null" and
"!= null" tests to produce known results even when the given value is
otherwise unknown.

This commit also includes some updates to test results that are now
refined based on cty's own built-in refinement behaviors, just as a
result of us having updated cty in the previous commit.
This commit is contained in:
Martin Atkins 2023-02-07 14:07:25 -08:00
parent 1ef550e59a
commit c912970153
12 changed files with 130 additions and 77 deletions

View File

@ -27,7 +27,8 @@ var CidrHostFunc = function.New(&function.Spec{
Type: cty.Number,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
var hostNum *big.Int
if err := gocty.FromCtyValue(args[1], &hostNum); err != nil {
@ -56,7 +57,8 @@ var CidrNetmaskFunc = function.New(&function.Spec{
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
_, network, err := ipaddr.ParseCIDR(args[0].AsString())
if err != nil {
@ -88,7 +90,8 @@ var CidrSubnetFunc = function.New(&function.Spec{
Type: cty.Number,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
var newbits int
if err := gocty.FromCtyValue(args[1], &newbits); err != nil {
@ -126,7 +129,8 @@ var CidrSubnetsFunc = function.New(&function.Spec{
Name: "newbits",
Type: cty.Number,
},
Type: function.StaticReturnType(cty.List(cty.String)),
Type: function.StaticReturnType(cty.List(cty.String)),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
_, network, err := ipaddr.ParseCIDR(args[0].AsString())
if err != nil {

View File

@ -35,6 +35,7 @@ var LengthFunc = function.New(&function.Spec{
return cty.Number, errors.New("argument must be a string, a collection type, or a structural type")
}
},
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
coll := args[0]
collTy := args[0].Type()
@ -71,7 +72,8 @@ var AllTrueFunc = function.New(&function.Spec{
Type: cty.List(cty.Bool),
},
},
Type: function.StaticReturnType(cty.Bool),
Type: function.StaticReturnType(cty.Bool),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
result := cty.True
for it := args[0].ElementIterator(); it.Next(); {
@ -100,7 +102,8 @@ var AnyTrueFunc = function.New(&function.Spec{
Type: cty.List(cty.Bool),
},
},
Type: function.StaticReturnType(cty.Bool),
Type: function.StaticReturnType(cty.Bool),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
result := cty.False
var hasUnknown bool
@ -149,6 +152,7 @@ var CoalesceFunc = function.New(&function.Spec{
}
return retType, nil
},
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
for _, argVal := range args {
// We already know this will succeed because of the checks in our Type func above
@ -181,7 +185,8 @@ var IndexFunc = function.New(&function.Spec{
Type: cty.DynamicPseudoType,
},
},
Type: function.StaticReturnType(cty.Number),
Type: function.StaticReturnType(cty.Number),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
if !(args[0].Type().IsListType() || args[0].Type().IsTupleType()) {
return cty.NilVal, errors.New("argument must be a list or tuple")
@ -346,6 +351,7 @@ var MatchkeysFunc = function.New(&function.Spec{
// the return type is based on args[0] (values)
return args[0].Type(), nil
},
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
if !args[0].IsKnown() {
return cty.UnknownVal(cty.List(retType.ElementType())), nil
@ -489,7 +495,8 @@ var SumFunc = function.New(&function.Spec{
Type: cty.DynamicPseudoType,
},
},
Type: function.StaticReturnType(cty.Number),
Type: function.StaticReturnType(cty.Number),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
if !args[0].CanIterateElements() {
@ -558,7 +565,8 @@ var TransposeFunc = function.New(&function.Spec{
Type: cty.Map(cty.List(cty.String)),
},
},
Type: function.StaticReturnType(cty.Map(cty.List(cty.String))),
Type: function.StaticReturnType(cty.Map(cty.List(cty.String))),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
inputMap := args[0]
if !inputMap.IsWhollyKnown() {

View File

@ -71,11 +71,15 @@ func TestLength(t *testing.T) {
},
{
cty.UnknownVal(cty.List(cty.Bool)),
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number).Refine().
NotNull().
NumberRangeLowerBound(cty.Zero, true).
NumberRangeUpperBound(cty.NumberIntVal(math.MaxInt), true).
NewValue(),
},
{
cty.DynamicVal,
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number).RefineNotNull(),
},
{
cty.StringVal("hello"),
@ -120,11 +124,10 @@ func TestLength(t *testing.T) {
},
{
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.Number),
},
{
cty.DynamicVal,
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number).Refine().
NotNull().
NumberRangeLowerBound(cty.Zero, true).
NewValue(),
},
{ // Marked collections return a marked length
cty.ListVal([]cty.Value{
@ -229,7 +232,7 @@ func TestAllTrue(t *testing.T) {
},
{
cty.ListVal([]cty.Value{cty.UnknownVal(cty.Bool)}),
cty.UnknownVal(cty.Bool),
cty.UnknownVal(cty.Bool).RefineNotNull(),
false,
},
{
@ -237,12 +240,12 @@ func TestAllTrue(t *testing.T) {
cty.UnknownVal(cty.Bool),
cty.UnknownVal(cty.Bool),
}),
cty.UnknownVal(cty.Bool),
cty.UnknownVal(cty.Bool).RefineNotNull(),
false,
},
{
cty.UnknownVal(cty.List(cty.Bool)),
cty.UnknownVal(cty.Bool),
cty.UnknownVal(cty.Bool).RefineNotNull(),
false,
},
{
@ -310,7 +313,7 @@ func TestAnyTrue(t *testing.T) {
},
{
cty.ListVal([]cty.Value{cty.UnknownVal(cty.Bool)}),
cty.UnknownVal(cty.Bool),
cty.UnknownVal(cty.Bool).RefineNotNull(),
false,
},
{
@ -318,7 +321,7 @@ func TestAnyTrue(t *testing.T) {
cty.UnknownVal(cty.Bool),
cty.False,
}),
cty.UnknownVal(cty.Bool),
cty.UnknownVal(cty.Bool).RefineNotNull(),
false,
},
{
@ -331,7 +334,7 @@ func TestAnyTrue(t *testing.T) {
},
{
cty.UnknownVal(cty.List(cty.Bool)),
cty.UnknownVal(cty.Bool),
cty.UnknownVal(cty.Bool).RefineNotNull(),
false,
},
{
@ -409,17 +412,17 @@ func TestCoalesce(t *testing.T) {
},
{
[]cty.Value{cty.UnknownVal(cty.Bool), cty.True},
cty.UnknownVal(cty.Bool),
cty.UnknownVal(cty.Bool).RefineNotNull(),
false,
},
{
[]cty.Value{cty.UnknownVal(cty.Bool), cty.StringVal("hello")},
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String).RefineNotNull(),
false,
},
{
[]cty.Value{cty.DynamicVal, cty.True},
cty.UnknownVal(cty.Bool),
cty.UnknownVal(cty.Bool).RefineNotNull(),
false,
},
{
@ -1065,7 +1068,7 @@ func TestMatchkeys(t *testing.T) {
cty.ListVal([]cty.Value{
cty.StringVal("ref1"),
}),
cty.UnknownVal(cty.List(cty.String)),
cty.UnknownVal(cty.List(cty.String)).RefineNotNull(),
false,
},
{ // different types that can be unified
@ -1529,7 +1532,7 @@ func TestSum(t *testing.T) {
cty.StringVal("b"),
cty.StringVal("c"),
}),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String).RefineNotNull(),
"argument must be list, set, or tuple of number values",
},
{
@ -1583,7 +1586,7 @@ func TestSum(t *testing.T) {
cty.StringVal("a"),
cty.NumberIntVal(38),
}),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String).RefineNotNull(),
"argument must be list, set, or tuple of number values",
},
{
@ -1603,17 +1606,17 @@ func TestSum(t *testing.T) {
},
{
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number).RefineNotNull(),
"",
},
{
cty.UnknownVal(cty.List(cty.Number)),
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number).RefineNotNull(),
"",
},
{ // known list containing unknown values
cty.ListVal([]cty.Value{cty.UnknownVal(cty.Number)}),
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number).RefineNotNull(),
"",
},
{ // numbers too large to represent as float64
@ -1707,7 +1710,7 @@ func TestTranspose(t *testing.T) {
cty.MapVal(map[string]cty.Value{
"key1": cty.UnknownVal(cty.List(cty.String)),
}),
cty.UnknownVal(cty.Map(cty.List(cty.String))),
cty.UnknownVal(cty.Map(cty.List(cty.String))).RefineNotNull(),
false,
},
{ // bad map - empty value

View File

@ -27,8 +27,9 @@ import (
)
var UUIDFunc = function.New(&function.Spec{
Params: []function.Parameter{},
Type: function.StaticReturnType(cty.String),
Params: []function.Parameter{},
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
result, err := uuid.GenerateUUID()
if err != nil {
@ -49,7 +50,8 @@ var UUIDV5Func = function.New(&function.Spec{
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
var namespace uuidv5.UUID
switch {
@ -103,7 +105,8 @@ var BcryptFunc = function.New(&function.Spec{
Name: "cost",
Type: cty.Number,
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
defaultCost := 10
@ -150,7 +153,8 @@ var RsaDecryptFunc = function.New(&function.Spec{
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
s := args[0].AsString()
key := args[1].AsString()
@ -225,7 +229,8 @@ func makeStringHashFunction(hf func() hash.Hash, enc func([]byte) string) functi
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
s := args[0].AsString()
h := hf()
@ -244,7 +249,8 @@ func makeFileHashFunction(baseDir string, hf func() hash.Hash, enc func([]byte)
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
path := args[0].AsString()
f, err := openFile(baseDir, path)

View File

@ -13,8 +13,9 @@ import (
// TimestampFunc constructs a function that returns a string representation of the current date and time.
var TimestampFunc = function.New(&function.Spec{
Params: []function.Parameter{},
Type: function.StaticReturnType(cty.String),
Params: []function.Parameter{},
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
return cty.StringVal(time.Now().UTC().Format(time.RFC3339)), nil
},
@ -44,7 +45,8 @@ var TimeAddFunc = function.New(&function.Spec{
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
ts, err := parseTimestamp(args[0].AsString())
if err != nil {
@ -71,7 +73,8 @@ var TimeCmpFunc = function.New(&function.Spec{
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.Number),
Type: function.StaticReturnType(cty.Number),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
tsA, err := parseTimestamp(args[0].AsString())
if err != nil {

View File

@ -56,13 +56,13 @@ func TestTimeadd(t *testing.T) {
{ // Invalid format timestamp
cty.StringVal("2017-11-22"),
cty.StringVal("-1h"),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String).RefineNotNull(),
true,
},
{ // Invalid format duration (day is not supported by ParseDuration)
cty.StringVal("2017-11-22T00:00:00Z"),
cty.StringVal("1d"),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String).RefineNotNull(),
true,
},
}
@ -132,31 +132,31 @@ func TestTimeCmp(t *testing.T) {
{
cty.StringVal("2017-11-22T00:00:00Z"),
cty.StringVal("bloop"),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String).RefineNotNull(),
`not a valid RFC3339 timestamp: cannot use "bloop" as year`,
},
{
cty.StringVal("2017-11-22 00:00:00Z"),
cty.StringVal("2017-11-22T00:00:00Z"),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String).RefineNotNull(),
`not a valid RFC3339 timestamp: missing required time introducer 'T'`,
},
{
cty.StringVal("2017-11-22T00:00:00Z"),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number).RefineNotNull(),
``,
},
{
cty.UnknownVal(cty.String),
cty.StringVal("2017-11-22T00:00:00Z"),
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number).RefineNotNull(),
``,
},
{
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number).RefineNotNull(),
``,
},
}

View File

@ -26,7 +26,8 @@ var Base64DecodeFunc = function.New(&function.Spec{
AllowMarked: true,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
str, strMarks := args[0].Unmark()
s := str.AsString()
@ -50,7 +51,8 @@ var Base64EncodeFunc = function.New(&function.Spec{
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
return cty.StringVal(base64.StdEncoding.EncodeToString([]byte(args[0].AsString()))), nil
},
@ -68,7 +70,8 @@ var TextEncodeBase64Func = function.New(&function.Spec{
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
encoding, err := ianaindex.IANA.Encoding(args[1].AsString())
if err != nil || encoding == nil {
@ -111,7 +114,8 @@ var TextDecodeBase64Func = function.New(&function.Spec{
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
encoding, err := ianaindex.IANA.Encoding(args[1].AsString())
if err != nil || encoding == nil {
@ -154,7 +158,8 @@ var Base64GzipFunc = function.New(&function.Spec{
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
s := args[0].AsString()
@ -181,7 +186,8 @@ var URLEncodeFunc = function.New(&function.Spec{
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
return cty.StringVal(url.QueryEscape(args[0].AsString())), nil
},

View File

@ -235,25 +235,25 @@ func TestBase64TextEncode(t *testing.T) {
{
cty.StringVal("abc123!?$*&()'-=@~"),
cty.StringVal("NOT-EXISTS"),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String).RefineNotNull(),
`"NOT-EXISTS" is not a supported IANA encoding name or alias in this Terraform version`,
},
{
cty.StringVal("🤔"),
cty.StringVal("cp437"),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String).RefineNotNull(),
`the given string contains characters that cannot be represented in IBM437`,
},
{
cty.UnknownVal(cty.String),
cty.StringVal("windows-1250"),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String).RefineNotNull(),
``,
},
{
cty.StringVal("hello world"),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String).RefineNotNull(),
``,
},
}
@ -309,13 +309,13 @@ func TestBase64TextDecode(t *testing.T) {
{
cty.StringVal("doesn't matter"),
cty.StringVal("NOT-EXISTS"),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String).RefineNotNull(),
`"NOT-EXISTS" is not a supported IANA encoding name or alias in this Terraform version`,
},
{
cty.StringVal("<invalid base64>"),
cty.StringVal("cp437"),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String).RefineNotNull(),
`the given value is has an invalid base64 symbol at offset 0`,
},
{
@ -327,13 +327,13 @@ func TestBase64TextDecode(t *testing.T) {
{
cty.UnknownVal(cty.String),
cty.StringVal("windows-1250"),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String).RefineNotNull(),
``,
},
{
cty.StringVal("YQBiAGMAMQAyADMAIQA/ACQAKgAmACgAKQAnAC0APQBAAH4A"),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String).RefineNotNull(),
``,
},
}

View File

@ -31,7 +31,8 @@ func MakeFileFunc(baseDir string, encBase64 bool) function.Function {
AllowMarked: true,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
pathArg, pathMarks := args[0].Unmark()
path := pathArg.AsString()
@ -201,7 +202,8 @@ func MakeFileExistsFunc(baseDir string) function.Function {
AllowMarked: true,
},
},
Type: function.StaticReturnType(cty.Bool),
Type: function.StaticReturnType(cty.Bool),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
pathArg, pathMarks := args[0].Unmark()
path := pathArg.AsString()
@ -273,7 +275,8 @@ func MakeFileSetFunc(baseDir string) function.Function {
AllowMarked: true,
},
},
Type: function.StaticReturnType(cty.Set(cty.String)),
Type: function.StaticReturnType(cty.Set(cty.String)),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
pathArg, pathMarks := args[0].Unmark()
path := pathArg.AsString()
@ -340,7 +343,8 @@ var BasenameFunc = function.New(&function.Spec{
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
return cty.StringVal(filepath.Base(args[0].AsString())), nil
},
@ -355,7 +359,8 @@ var DirnameFunc = function.New(&function.Spec{
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
return cty.StringVal(filepath.Dir(args[0].AsString())), nil
},
@ -369,7 +374,8 @@ var AbsPathFunc = function.New(&function.Spec{
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
absPath, err := filepath.Abs(args[0].AsString())
return cty.StringVal(filepath.ToSlash(absPath)), err
@ -384,7 +390,8 @@ var PathExpandFunc = function.New(&function.Spec{
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
homePath, err := homedir.Expand(args[0].AsString())

View File

@ -24,7 +24,8 @@ var LogFunc = function.New(&function.Spec{
Type: cty.Number,
},
},
Type: function.StaticReturnType(cty.Number),
Type: function.StaticReturnType(cty.Number),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
var num float64
if err := gocty.FromCtyValue(args[0], &num); err != nil {
@ -52,7 +53,8 @@ var PowFunc = function.New(&function.Spec{
Type: cty.Number,
},
},
Type: function.StaticReturnType(cty.Number),
Type: function.StaticReturnType(cty.Number),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
var num float64
if err := gocty.FromCtyValue(args[0], &num); err != nil {
@ -77,7 +79,8 @@ var SignumFunc = function.New(&function.Spec{
Type: cty.Number,
},
},
Type: function.StaticReturnType(cty.Number),
Type: function.StaticReturnType(cty.Number),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
var num int
if err := gocty.FromCtyValue(args[0], &num); err != nil {
@ -115,6 +118,7 @@ var ParseIntFunc = function.New(&function.Spec{
}
return cty.Number, nil
},
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
var numstr string

View File

@ -0,0 +1,9 @@
package funcs
import (
"github.com/zclconf/go-cty/cty"
)
func refineNotNull(b *cty.RefinementBuilder) *cty.RefinementBuilder {
return b.NotNull()
}

View File

@ -24,7 +24,8 @@ var StartsWithFunc = function.New(&function.Spec{
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.Bool),
Type: function.StaticReturnType(cty.Bool),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
str := args[0].AsString()
prefix := args[1].AsString()
@ -50,7 +51,8 @@ var EndsWithFunc = function.New(&function.Spec{
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.Bool),
Type: function.StaticReturnType(cty.Bool),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
str := args[0].AsString()
suffix := args[1].AsString()
@ -80,7 +82,8 @@ var ReplaceFunc = function.New(&function.Spec{
Type: cty.String,
},
},
Type: function.StaticReturnType(cty.String),
Type: function.StaticReturnType(cty.String),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
str := args[0].AsString()
substr := args[1].AsString()