Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reflect: Determine equivalency of float32 or float64, and *big.Float via string representation #919

Merged
merged 4 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 38 additions & 108 deletions internal/reflect/number.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import (
"reflect"
"strconv"

"github.com/hashicorp/terraform-plugin-go/tftypes"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/attr/xattr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

// Number creates a *big.Float and populates it with the data in `val`. It then
Expand All @@ -24,9 +25,7 @@ import (
// *big.Int).
//
// Number will loudly fail when a number cannot be losslessly represented using
// the requested type, unless opts.AllowRoundingNumbers is set to true. This
// setting is mildly dangerous, because Terraform does not like when you round
// things, as a general rule of thumb.
// the requested type.
//
// It is meant to be called through Into, not directly.
func Number(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path path.Path) (reflect.Value, diag.Diagnostics) {
Expand All @@ -53,73 +52,50 @@ func Number(ctx context.Context, typ attr.Type, val tftypes.Value, target reflec
return reflect.ValueOf(result), diags
case reflect.TypeOf(big.NewInt(0)):
intResult, acc := result.Int(nil)
if acc != big.Exact && !opts.AllowRoundingNumbers {
if acc != big.Exact {
return reflect.ValueOf(result), append(diags, roundingErrorDiag)
}
return reflect.ValueOf(intResult), diags
}

switch target.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64:
intResult, acc := result.Int64()
if acc != big.Exact && !opts.AllowRoundingNumbers {
if acc != big.Exact {
return target, append(diags, roundingErrorDiag)
}
switch target.Kind() {
case reflect.Int:
if strconv.IntSize == 32 && intResult > math.MaxInt32 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
intResult = math.MaxInt32
return target, append(diags, roundingErrorDiag)
}
if strconv.IntSize == 32 && intResult < math.MinInt32 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
intResult = math.MinInt32
return target, append(diags, roundingErrorDiag)
}
return reflect.ValueOf(int(intResult)), diags
case reflect.Int8:
if intResult > math.MaxInt8 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
intResult = math.MaxInt8
return target, append(diags, roundingErrorDiag)
}
if intResult < math.MinInt8 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
intResult = math.MinInt8
return target, append(diags, roundingErrorDiag)
}
return reflect.ValueOf(int8(intResult)), diags
case reflect.Int16:
if intResult > math.MaxInt16 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
intResult = math.MaxInt16
return target, append(diags, roundingErrorDiag)
}
if intResult < math.MinInt16 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
intResult = math.MinInt16
return target, append(diags, roundingErrorDiag)
}
return reflect.ValueOf(int16(intResult)), diags
case reflect.Int32:
if intResult > math.MaxInt32 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
intResult = math.MaxInt32
return target, append(diags, roundingErrorDiag)
}
if intResult < math.MinInt32 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
intResult = math.MinInt32
return target, append(diags, roundingErrorDiag)
}
return reflect.ValueOf(int32(intResult)), diags
case reflect.Int64:
Expand All @@ -128,113 +104,67 @@ func Number(ctx context.Context, typ attr.Type, val tftypes.Value, target reflec
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
reflect.Uint64:
uintResult, acc := result.Uint64()
if acc != big.Exact && !opts.AllowRoundingNumbers {
if acc != big.Exact {
return target, append(diags, roundingErrorDiag)
}
switch target.Kind() {
case reflect.Uint:
if strconv.IntSize == 32 && uintResult > math.MaxUint32 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
uintResult = math.MaxUint32
return target, append(diags, roundingErrorDiag)
}
return reflect.ValueOf(uint(uintResult)), diags
case reflect.Uint8:
if uintResult > math.MaxUint8 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
uintResult = math.MaxUint8
return target, append(diags, roundingErrorDiag)
}
return reflect.ValueOf(uint8(uintResult)), diags
case reflect.Uint16:
if uintResult > math.MaxUint16 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
uintResult = math.MaxUint16
return target, append(diags, roundingErrorDiag)
}
return reflect.ValueOf(uint16(uintResult)), diags
case reflect.Uint32:
if uintResult > math.MaxUint32 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
uintResult = math.MaxUint32
return target, append(diags, roundingErrorDiag)
}
return reflect.ValueOf(uint32(uintResult)), diags
case reflect.Uint64:
return reflect.ValueOf(uintResult), diags
}
case reflect.Float32:
floatResult, acc := result.Float32()
if acc != big.Exact && !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
} else if acc == big.Above {
floatResult = math.MaxFloat32
} else if acc == big.Below {
floatResult = math.SmallestNonzeroFloat32
} else if acc != big.Exact {
err := fmt.Errorf("unsure how to round %s and %f", acc, floatResult)
diags.AddAttributeError(
path,
"Value Conversion Error",
"An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(),
)
floatResult, _ := result.Float32()

bf := big.NewFloat(float64(floatResult))

if result.Text('f', -1) != bf.Text('f', -1) {
diags.Append(roundingErrorDiag)

return target, diags
}

return reflect.ValueOf(floatResult), diags
case reflect.Float64:
floatResult, acc := result.Float64()
if acc != big.Exact && !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
if acc == big.Above {
if floatResult == math.Inf(1) || floatResult == math.MaxFloat64 {
floatResult = math.MaxFloat64
} else if floatResult == 0.0 || floatResult == math.SmallestNonzeroFloat64 {
floatResult = -math.SmallestNonzeroFloat64
} else {
err := fmt.Errorf("not sure how to round %s and %f", acc, floatResult)
diags.AddAttributeError(
path,
"Value Conversion Error",
"An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(),
)
return target, diags
}
} else if acc == big.Below {
if floatResult == math.Inf(-1) || floatResult == -math.MaxFloat64 {
floatResult = -math.MaxFloat64
} else if floatResult == -0.0 || floatResult == -math.SmallestNonzeroFloat64 { //nolint:staticcheck
floatResult = math.SmallestNonzeroFloat64
} else {
err := fmt.Errorf("not sure how to round %s and %f", acc, floatResult)
diags.AddAttributeError(
path,
"Value Conversion Error",
"An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(),
)
return target, diags
}
} else if acc != big.Exact {
err := fmt.Errorf("not sure how to round %s and %f", acc, floatResult)
diags.AddAttributeError(
path,
"Value Conversion Error",
"An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(),
)
floatResult, _ := result.Float64()

bf := big.NewFloat(floatResult)

if result.Text('f', -1) != bf.Text('f', -1) {
diags.Append(roundingErrorDiag)

return target, diags
}

return reflect.ValueOf(floatResult), diags
}

err = fmt.Errorf("cannot convert number to %s", target.Type())

diags.AddAttributeError(
path,
"Value Conversion Error",
"An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(),
)

return target, diags
}

Expand Down
Loading
Loading