Skip to content

Commit 7416265

Browse files
cty: Refine the ranges of arithmetic results
If we are performing addition, subtraction, or multiplication on unknown numbers with known numeric bounds then we can propagate bounds to the result by performing interval arithmetic. This is not as complete as it could be because of trying to share a single implementation across all of the functions while still dealing with all of their panic edge cases.
1 parent 448ca74 commit 7416265

File tree

5 files changed

+264
-14
lines changed

5 files changed

+264
-14
lines changed

cty/msgpack/unknown.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func marshalUnknownValue(rng cty.ValueRange, path cty.Path, enc *msgpack.Encoder
6363
lower, lowerInc := rng.NumberLowerBound()
6464
upper, upperInc := rng.NumberUpperBound()
6565
boundTy := cty.Tuple([]cty.Type{cty.Number, cty.Bool})
66-
if lower.IsKnown() {
66+
if lower.IsKnown() && lower != cty.NegativeInfinity {
6767
mapLen++
6868
refnEnc.EncodeInt(int64(unknownValNumberMin))
6969
marshal(
@@ -73,7 +73,7 @@ func marshalUnknownValue(rng cty.ValueRange, path cty.Path, enc *msgpack.Encoder
7373
refnEnc,
7474
)
7575
}
76-
if upper.IsKnown() {
76+
if upper.IsKnown() && upper != cty.PositiveInfinity {
7777
mapLen++
7878
refnEnc.EncodeInt(int64(unknownValNumberMax))
7979
marshal(

cty/unknown_refinement.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,10 +632,10 @@ func (r *refinementNumber) rawEqual(other unknownValRefinement) bool {
632632
func (r *refinementNumber) GoString() string {
633633
var b strings.Builder
634634
b.WriteString(r.refinementNullable.GoString())
635-
if r.min != NilVal {
635+
if r.min != NilVal && r.min != NegativeInfinity {
636636
fmt.Fprintf(&b, ".NumberLowerBound(%#v, %t)", r.min, r.minInc)
637637
}
638-
if r.max != NilVal {
638+
if r.max != NilVal && r.max != PositiveInfinity {
639639
fmt.Fprintf(&b, ".NumberUpperBound(%#v, %t)", r.max, r.maxInc)
640640
}
641641
return b.String()

cty/value_ops.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,8 @@ func (val Value) Add(other Value) Value {
593593

594594
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
595595
shortCircuit = forceShortCircuitType(shortCircuit, Number)
596-
return (*shortCircuit).RefineNotNull()
596+
ret := shortCircuit.RefineWith(numericRangeArithmetic(Value.Add, val.Range(), other.Range()))
597+
return ret.RefineNotNull()
597598
}
598599

599600
ret := new(big.Float)
@@ -612,7 +613,8 @@ func (val Value) Subtract(other Value) Value {
612613

613614
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
614615
shortCircuit = forceShortCircuitType(shortCircuit, Number)
615-
return (*shortCircuit).RefineNotNull()
616+
ret := shortCircuit.RefineWith(numericRangeArithmetic(Value.Subtract, val.Range(), other.Range()))
617+
return ret.RefineNotNull()
616618
}
617619

618620
return val.Add(other.Negate())
@@ -646,7 +648,8 @@ func (val Value) Multiply(other Value) Value {
646648

647649
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
648650
shortCircuit = forceShortCircuitType(shortCircuit, Number)
649-
return (*shortCircuit).RefineNotNull()
651+
ret := shortCircuit.RefineWith(numericRangeArithmetic(Value.Multiply, val.Range(), other.Range()))
652+
return ret.RefineNotNull()
650653
}
651654

652655
// find the larger precision of the arguments
@@ -691,6 +694,9 @@ func (val Value) Divide(other Value) Value {
691694

692695
if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil {
693696
shortCircuit = forceShortCircuitType(shortCircuit, Number)
697+
// TODO: We could potentially refine the range of the result here, but
698+
// we don't right now because our division operation is not monotone
699+
// if the denominator could potentially be zero.
694700
return (*shortCircuit).RefineNotNull()
695701
}
696702

cty/value_ops_test.go

Lines changed: 187 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1767,6 +1767,66 @@ func TestValueAdd(t *testing.T) {
17671767
UnknownVal(Number),
17681768
UnknownVal(Number).RefineNotNull(),
17691769
},
1770+
{
1771+
NumberIntVal(1),
1772+
UnknownVal(Number).Refine().
1773+
NumberRangeLowerBound(NumberIntVal(2), false).
1774+
NewValue(),
1775+
UnknownVal(Number).Refine().
1776+
NotNull().
1777+
NumberRangeLowerBound(NumberIntVal(3), true).
1778+
NewValue(),
1779+
},
1780+
{
1781+
Zero,
1782+
UnknownVal(Number).Refine().
1783+
NumberRangeLowerBound(NumberIntVal(2), false).
1784+
NewValue(),
1785+
UnknownVal(Number).Refine().
1786+
NotNull().
1787+
NumberRangeLowerBound(NumberIntVal(2), true).
1788+
NewValue(),
1789+
},
1790+
{
1791+
UnknownVal(Number).Refine().
1792+
NumberRangeLowerBound(NumberIntVal(2), false).
1793+
NewValue(),
1794+
UnknownVal(Number).Refine().
1795+
NumberRangeLowerBound(NumberIntVal(2), false).
1796+
NewValue(),
1797+
UnknownVal(Number).Refine().
1798+
NotNull().
1799+
NumberRangeLowerBound(NumberIntVal(4), true).
1800+
NewValue(),
1801+
},
1802+
{
1803+
UnknownVal(Number).Refine().
1804+
NumberRangeLowerBound(NumberIntVal(1), true).
1805+
NumberRangeUpperBound(NumberIntVal(2), false).
1806+
NewValue(),
1807+
UnknownVal(Number).Refine().
1808+
NumberRangeLowerBound(NumberIntVal(2), false).
1809+
NewValue(),
1810+
UnknownVal(Number).Refine().
1811+
NotNull().
1812+
NumberRangeLowerBound(NumberIntVal(3), true).
1813+
NewValue(),
1814+
},
1815+
{
1816+
UnknownVal(Number).Refine().
1817+
NumberRangeLowerBound(NumberIntVal(1), true).
1818+
NumberRangeUpperBound(NumberIntVal(2), false).
1819+
NewValue(),
1820+
UnknownVal(Number).Refine().
1821+
NumberRangeLowerBound(NumberIntVal(2), false).
1822+
NumberRangeUpperBound(NumberIntVal(3), false).
1823+
NewValue(),
1824+
UnknownVal(Number).Refine().
1825+
NotNull().
1826+
NumberRangeLowerBound(NumberIntVal(3), true).
1827+
NumberRangeUpperBound(NumberIntVal(5), true).
1828+
NewValue(),
1829+
},
17701830
{
17711831
UnknownVal(Number),
17721832
UnknownVal(Number),
@@ -1803,7 +1863,7 @@ func TestValueAdd(t *testing.T) {
18031863
t.Run(fmt.Sprintf("%#v.Add(%#v)", test.LHS, test.RHS), func(t *testing.T) {
18041864
got := test.LHS.Add(test.RHS)
18051865
if !got.RawEquals(test.Expected) {
1806-
t.Fatalf("Add returned %#v; want %#v", got, test.Expected)
1866+
t.Fatalf("Wrong result\ngot: %#v\nwant: %#v", got, test.Expected)
18071867
}
18081868
})
18091869
}
@@ -1840,6 +1900,63 @@ func TestValueSubtract(t *testing.T) {
18401900
UnknownVal(Number),
18411901
UnknownVal(Number).RefineNotNull(),
18421902
},
1903+
{
1904+
NumberIntVal(1),
1905+
UnknownVal(Number).Refine().
1906+
NumberRangeLowerBound(NumberIntVal(2), true).
1907+
NewValue(),
1908+
UnknownVal(Number).Refine().
1909+
NotNull().
1910+
NumberRangeUpperBound(NumberIntVal(-1), true).
1911+
NewValue(),
1912+
},
1913+
{
1914+
Zero,
1915+
UnknownVal(Number).Refine().
1916+
NumberRangeLowerBound(NumberIntVal(2), true).
1917+
NewValue(),
1918+
UnknownVal(Number).Refine().
1919+
NotNull().
1920+
NumberRangeUpperBound(NumberIntVal(-2), true).
1921+
NewValue(),
1922+
},
1923+
{
1924+
UnknownVal(Number).Refine().
1925+
NumberRangeLowerBound(NumberIntVal(2), true).
1926+
NewValue(),
1927+
UnknownVal(Number).Refine().
1928+
NumberRangeLowerBound(NumberIntVal(2), true).
1929+
NewValue(),
1930+
UnknownVal(Number).RefineNotNull(), // We don't currently refine this case
1931+
},
1932+
{
1933+
UnknownVal(Number).Refine().
1934+
NumberRangeLowerBound(NumberIntVal(1), true).
1935+
NumberRangeUpperBound(NumberIntVal(2), false).
1936+
NewValue(),
1937+
UnknownVal(Number).Refine().
1938+
NumberRangeLowerBound(NumberIntVal(2), true).
1939+
NewValue(),
1940+
UnknownVal(Number).Refine().
1941+
NotNull().
1942+
NumberRangeUpperBound(NumberIntVal(0), true).
1943+
NewValue(),
1944+
},
1945+
{
1946+
UnknownVal(Number).Refine().
1947+
NumberRangeLowerBound(NumberIntVal(1), true).
1948+
NumberRangeUpperBound(NumberIntVal(2), false).
1949+
NewValue(),
1950+
UnknownVal(Number).Refine().
1951+
NumberRangeLowerBound(NumberIntVal(2), false).
1952+
NumberRangeUpperBound(NumberIntVal(3), false).
1953+
NewValue(),
1954+
UnknownVal(Number).Refine().
1955+
NotNull().
1956+
NumberRangeLowerBound(NumberIntVal(-2), true).
1957+
NumberRangeUpperBound(NumberIntVal(0), true).
1958+
NewValue(),
1959+
},
18431960
{
18441961
NumberIntVal(1),
18451962
DynamicVal,
@@ -1871,7 +1988,7 @@ func TestValueSubtract(t *testing.T) {
18711988
t.Run(fmt.Sprintf("%#v.Subtract(%#v)", test.LHS, test.RHS), func(t *testing.T) {
18721989
got := test.LHS.Subtract(test.RHS)
18731990
if !got.RawEquals(test.Expected) {
1874-
t.Fatalf("Subtract returned %#v; want %#v", got, test.Expected)
1991+
t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, test.Expected)
18751992
}
18761993
})
18771994
}
@@ -1908,7 +2025,7 @@ func TestValueNegate(t *testing.T) {
19082025
t.Run(fmt.Sprintf("%#v.Negate()", test.Receiver), func(t *testing.T) {
19092026
got := test.Receiver.Negate()
19102027
if !got.RawEquals(test.Expected) {
1911-
t.Fatalf("Negate returned %#v; want %#v", got, test.Expected)
2028+
t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, test.Expected)
19122029
}
19132030
})
19142031
}
@@ -1945,6 +2062,71 @@ func TestValueMultiply(t *testing.T) {
19452062
UnknownVal(Number),
19462063
UnknownVal(Number).RefineNotNull(),
19472064
},
2065+
{
2066+
NumberIntVal(3),
2067+
UnknownVal(Number).Refine().
2068+
NumberRangeLowerBound(NumberIntVal(2), false).
2069+
NewValue(),
2070+
UnknownVal(Number).Refine().
2071+
NotNull().
2072+
NumberRangeLowerBound(NumberIntVal(6), true).
2073+
NewValue(),
2074+
},
2075+
{
2076+
Zero,
2077+
UnknownVal(Number).Refine().
2078+
NumberRangeLowerBound(NumberIntVal(2), false).
2079+
NewValue(),
2080+
UnknownVal(Number).RefineNotNull(), // We can't currently refine this case
2081+
},
2082+
{
2083+
UnknownVal(Number).Refine().
2084+
NumberRangeLowerBound(NumberIntVal(2), false).
2085+
NewValue(),
2086+
UnknownVal(Number).Refine().
2087+
NumberRangeLowerBound(NumberIntVal(4), false).
2088+
NewValue(),
2089+
UnknownVal(Number).Refine().
2090+
NotNull().
2091+
NumberRangeLowerBound(NumberIntVal(8), true).
2092+
NewValue(),
2093+
},
2094+
{
2095+
UnknownVal(Number).Refine().
2096+
NumberRangeLowerBound(NumberIntVal(3), true).
2097+
NumberRangeUpperBound(NumberIntVal(4), false).
2098+
NewValue(),
2099+
UnknownVal(Number).Refine().
2100+
NumberRangeLowerBound(NumberIntVal(2), false).
2101+
NewValue(),
2102+
UnknownVal(Number).Refine().
2103+
NotNull().
2104+
NumberRangeLowerBound(NumberIntVal(6), true).
2105+
NewValue(),
2106+
},
2107+
{
2108+
UnknownVal(Number).Refine().
2109+
NumberRangeLowerBound(NumberIntVal(1), true).
2110+
NumberRangeUpperBound(NumberIntVal(2), false).
2111+
NewValue(),
2112+
UnknownVal(Number).Refine().
2113+
NumberRangeLowerBound(NumberIntVal(2), false).
2114+
NumberRangeUpperBound(NumberIntVal(3), false).
2115+
NewValue(),
2116+
UnknownVal(Number).Refine().
2117+
NotNull().
2118+
NumberRangeLowerBound(NumberIntVal(2), true).
2119+
NumberRangeUpperBound(NumberIntVal(6), true).
2120+
NewValue(),
2121+
},
2122+
{
2123+
UnknownVal(Number).Refine().
2124+
NumberRangeLowerBound(NumberIntVal(1), true).
2125+
NumberRangeUpperBound(NumberIntVal(2), false).
2126+
NewValue(),
2127+
Zero,
2128+
Zero, // deduced by refinement
2129+
},
19482130
{
19492131
NumberIntVal(1),
19502132
DynamicVal,
@@ -1986,7 +2168,7 @@ func TestValueMultiply(t *testing.T) {
19862168
t.Run(fmt.Sprintf("%#v.Multiply(%#v)", test.LHS, test.RHS), func(t *testing.T) {
19872169
got := test.LHS.Multiply(test.RHS)
19882170
if !got.RawEquals(test.Expected) {
1989-
t.Fatalf("Multiply returned %#v; want %#v", got, test.Expected)
2171+
t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, test.Expected)
19902172
}
19912173
})
19922174
}
@@ -2064,7 +2246,7 @@ func TestValueDivide(t *testing.T) {
20642246
t.Run(fmt.Sprintf("%#v.Divide(%#v)", test.LHS, test.RHS), func(t *testing.T) {
20652247
got := test.LHS.Divide(test.RHS)
20662248
if !got.RawEquals(test.Expected) {
2067-
t.Fatalf("Divide returned %#v; want %#v", got, test.Expected)
2249+
t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, test.Expected)
20682250
}
20692251
})
20702252
}

0 commit comments

Comments
 (0)