Skip to content

Commit 2031bdc

Browse files
authored
Merge pull request #2963 from ultramiraculous/failable-int-int
2 parents 5063a7d + 1858b3e commit 2031bdc

File tree

3 files changed

+185
-100
lines changed

3 files changed

+185
-100
lines changed

stdlib/public/core/FixedPoint.swift.gyb

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,6 @@ public struct ${Self}
263263
}
264264
% end
265265

266-
/// Create an instance initialized to `value`.
267-
@_transparent public
268-
init(_ value: ${Self}) { self = value }
269266

270267
% if bits > 8:
271268
/// Creates an integer from its big-endian representation, changing the
@@ -436,44 +433,72 @@ extension ${Self} {
436433
extension ${Self} : SignedNumber {}
437434
% end
438435

439-
// Construction from other integer types
440-
@_transparent
441-
extension ${Self} {
442-
% for src_ty in all_integer_types(word_bits):
443-
% srcBits = src_ty.bits
444-
% srcSigned = src_ty.is_signed
445-
% Src = src_ty.stdlib_name
446-
% (srcSign, srcExt) = ('s', 'sext') if srcSigned else ('u', 'zext')
447-
% if Self != Src:
448-
public init(_ v: ${Src}) {
436+
437+
%{
438+
import gyb
439+
440+
fixed_fixed_conversion_function = gyb.parse_template("fixed_fixed_conversion_function",
441+
"""
449442
%
450-
let srcNotWord = v._value
443+
let src = value._value
444+
let result: (value: Builtin.${BuiltinName}, error: Builtin.Int1)
451445
%
452-
% if srcBits == bits and srcSign == sign:
453-
let dstNotWord = srcNotWord
446+
% if srcBits == bits and srcSign == sign: # Exact same size/signedness.
447+
result = (src, false._value)
454448
%
455-
% elif srcBits == bits:
456-
let tmp = Builtin.${srcSign}_to_${sign}_checked_conversion_Int${srcBits}(srcNotWord)
457-
Builtin.condfail(tmp.1)
458-
let dstNotWord = tmp.0
449+
% elif srcBits == bits: # Same size, switching signs.
450+
result = Builtin.${srcSign}_to_${sign}_checked_conversion_Int${srcBits}(src)
459451
%
460-
% elif srcBits > bits:
461-
let tmp = Builtin.${srcSign}_to_${sign}_checked_trunc_Int${srcBits}_Int${bits}(srcNotWord)
462-
Builtin.condfail(tmp.1)
463-
let dstNotWord = tmp.0
452+
% elif srcBits > bits: # Larger input, check for truncation.
453+
result = Builtin.${srcSign}_to_${sign}_checked_trunc_Int${srcBits}_Int${bits}(src)
464454
%
465-
% elif srcSigned and not signed:
466-
let tmp = Builtin.s_to_u_checked_conversion_Int${srcBits}(srcNotWord)
467-
Builtin.condfail(tmp.1)
468-
let dstNotWord = Builtin.${srcExt}_Int${srcBits}_Int${bits}(tmp.0)
455+
% elif srcSigned and not signed: # Smaller size input, signed going to unsigned.
456+
let (tmp, signError) = Builtin.s_to_u_checked_conversion_Int${srcBits}(src)
457+
result = (Builtin.${srcExt}_Int${srcBits}_Int${bits}(tmp), signError)
469458
%
470-
% else:
471-
let dstNotWord = Builtin.${srcExt}_Int${srcBits}_Int${bits}(srcNotWord)
459+
% else: # Smaller size input, unsigned to signed or unsigned to unsigned.
460+
result = (Builtin.${srcExt}_Int${srcBits}_Int${bits}(src), false._value)
472461
% end
473-
%
474-
self._value = dstNotWord
462+
%
463+
% if not safelyConvertable:
464+
${error_check}
465+
% end
466+
self._value = result.value
467+
""")
468+
}%
469+
470+
% for src_ty in all_integer_types(word_bits):
471+
% srcBits = src_ty.bits
472+
% srcSigned = src_ty.is_signed
473+
% Src = src_ty.stdlib_name
474+
% (srcSign, srcExt) = ('s', 'sext') if srcSigned else ('u', 'zext')
475+
% safelyConvertable = (srcBits < bits and (srcSign == sign or signed)) or (srcBits == bits and srcSign == sign)
476+
477+
@_transparent
478+
extension ${Self} {
479+
480+
@_transparent
481+
public init(_ value: ${Src}) {
482+
${gyb.execute_template(
483+
fixed_fixed_conversion_function,
484+
error_check="Builtin.condfail(result.error)",
485+
**locals()
486+
)
487+
}
488+
}
489+
490+
% if safelyConvertable:
491+
@available(*, message: "Converting ${Src} to ${Self} will always succeed.")
492+
% end
493+
@_transparent
494+
public init?(exactly value: ${Src}) {
495+
${gyb.execute_template(
496+
fixed_fixed_conversion_function,
497+
error_check="if Bool(result.error) == true { return nil }",
498+
**locals()
499+
)
500+
}
475501
}
476-
% end
477502

478503
% if should_define_truncating_bit_pattern_init(src_ty=src_ty, dst_ty=self_ty):
479504
/// Construct a `${Self}` having the same bitwise representation as
@@ -483,20 +508,22 @@ extension ${Self} {
483508
@_transparent
484509
public init(truncatingBitPattern: ${Src}) {
485510
%
486-
let srcNotWord = truncatingBitPattern._value
511+
let src = truncatingBitPattern._value
487512
%
488513
% if self_ty.bits == src_ty.bits:
489-
let dstNotWord = srcNotWord
514+
let dstNotWord = src
490515
% else:
491-
let dstNotWord = Builtin.trunc_Int${srcBits}_Int${bits}(srcNotWord)
516+
let dstNotWord = Builtin.trunc_Int${srcBits}_Int${bits}(src)
492517
% end
493518
%
494519
self._value = dstNotWord
495520
}
496521

497522
% end
523+
}
498524
% end
499525

526+
extension ${Self} {
500527
/// Construct a `${Self}` having the same memory representation as
501528
/// the `${OtherSelf}` `bitPattern`. No range or overflow checking
502529
/// occurs, and the resulting `${Self}` may not have the same numeric

validation-test/stdlib/FixedPointArithmeticTraps.swift.gyb

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -299,69 +299,5 @@ FixedPointArithmeticTraps.test("${description}/${IntTy}/Max") {
299299

300300
% end
301301

302-
// FIXME: these tests should be more thorough, and test all combinations of
303-
// types and error conditions.
304-
var FixedPointTruncationTraps = TestSuite("FixedPointTruncationTraps")
305-
306-
FixedPointTruncationTraps.test("SignedToSignedTruncation/dest=sign-overflow") {
307-
// Test that we check if we overflow on the sign bit.
308-
var x = getInt16(128)
309-
expectCrashLater()
310-
var result = Int8(x)
311-
_blackHole(result)
312-
}
313-
314-
FixedPointTruncationTraps.test("SignedToUnsignedTruncation/src=-1") {
315-
var x = getInt32(-1)
316-
expectCrashLater()
317-
var result = UInt8(x)
318-
_blackHole(result)
319-
}
320-
321-
FixedPointTruncationTraps.test("SignedToUnsignedSameSize/src=min") {
322-
var x = getInt8(-128)
323-
expectCrashLater()
324-
var result = UInt16(x)
325-
_blackHole(result)
326-
}
327-
328-
329-
FixedPointTruncationTraps.test("SignedToUnsignedTruncation/src=max") {
330-
var x = getInt32(0xFFFFFFF)
331-
expectCrashLater()
332-
var result = UInt16(x)
333-
_blackHole(result)
334-
}
335-
336-
FixedPointTruncationTraps.test("UnsignedToSignedTruncation/dest=sign-overflow") {
337-
// Test that we check if we overflow on the sign bit.
338-
var x = getUInt16(128)
339-
expectCrashLater()
340-
var result = Int8(x)
341-
_blackHole(result)
342-
}
343-
344-
FixedPointTruncationTraps.test("UnsignedToUnsignedTruncation/src=max") {
345-
var x = getUInt32(0xFFFFFFFF)
346-
expectCrashLater()
347-
var result = UInt16(x)
348-
_blackHole(result)
349-
}
350-
351-
// Same size conversions.
352-
FixedPointTruncationTraps.test("SignedToUnsignedSameSize") {
353-
var x = getInt8(-2)
354-
expectCrashLater()
355-
var result = UInt8(x)
356-
_blackHole(result)
357-
}
358-
359-
FixedPointTruncationTraps.test("UnsignedToSignedSameSize") {
360-
var x = getUInt8(128)
361-
expectCrashLater()
362-
var result = Int8(x)
363-
_blackHole(result)
364-
}
365-
366302
runAllTests()
367303

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// RUN: rm -rf %t
2+
// RUN: mkdir -p %t
3+
// RUN: %S/../../utils/gyb %s -o %t/FixedPointConversion.swift
4+
// RUN: %S/../../utils/line-directive %t/FixedPointConversion.swift -- %target-build-swift %t/FixedPointConversion.swift -o %t/a.out_Debug
5+
// RUN: %S/../../utils/line-directive %t/FixedPointConversion.swift -- %target-build-swift %t/FixedPointConversion.swift -o %t/a.out_Release -O
6+
//
7+
// RUN: %S/../../utils/line-directive %t/FixedPointConversion.swift -- %target-run %t/a.out_Debug
8+
// RUN: %S/../../utils/line-directive %t/FixedPointConversion.swift -- %target-run %t/a.out_Release
9+
// REQUIRES: executable_test
10+
11+
%{
12+
import gyb
13+
}%
14+
15+
import StdlibUnittest
16+
17+
var FixedPointConversionTraps = TestSuite("FixedPointToFixedPointConversionTraps")
18+
var FixedPointConversionFailure = TestSuite("FixedPointToFixedPointConversionFailures")
19+
20+
%{
21+
22+
int_to_int_conversion_template = gyb.parse_template("int_to_int_conversion",
23+
"""
24+
%{
25+
from SwiftIntTypes import all_integer_types
26+
27+
def intMax(bits, signed):
28+
bits = bits - 1 if signed else bits
29+
return (1 << bits) - 1
30+
31+
def intMin(bits, signed):
32+
return -1 * intMax(bits, signed) - 1 if signed else 0
33+
34+
}%
35+
36+
% for self_ty in all_integer_types(word_bits):
37+
% selfBits = self_ty.bits
38+
% selfSigned = self_ty.is_signed
39+
% selfMin = intMin(selfBits, selfSigned)
40+
% selfMax = intMax(selfBits, selfSigned)
41+
% Self = self_ty.stdlib_name
42+
43+
% for other_ty in all_integer_types(word_bits):
44+
% otherBits = other_ty.bits
45+
% otherSigned = other_ty.is_signed
46+
% otherMin = intMin(otherBits, otherSigned)
47+
% otherMax = intMax(otherBits, otherSigned)
48+
% Other = other_ty.stdlib_name
49+
50+
% for testValue in [selfMin, selfMax, selfMin - 1, selfMax + 1, otherMin, otherMax]:
51+
52+
% if testValue < otherMin or testValue > otherMax:
53+
% # Can't construct `other` value, do nothing and continue.
54+
55+
% elif testValue >= selfMin and testValue <= selfMax:
56+
57+
/// Always-safe conversion from ${Other}(${testValue}) to ${Self}.
58+
FixedPointConversionTraps.test("${Other}To${Self}Conversion/dest=${testValue}") {
59+
// Test that nothing interesting happens and we end up with the same result after converting.
60+
let input = get${Other}(${testValue})
61+
let result = ${Self}(input)
62+
expectEqual(${testValue}, result)
63+
_blackHole(result)
64+
}
65+
66+
/// Never-nil failable conversion from ${Other}(${testValue}) to ${Self}.
67+
FixedPointConversionFailure.test("${Other}To${Self}FailableConversion/dest=${testValue}") {
68+
// Test that nothing interesting happens and we end up with a non-nil, identical result.
69+
let input = get${Other}(${testValue})
70+
var result = ${Self}(exactly: input)
71+
expectNotEqual(result, nil)
72+
expectEqual(${testValue}, result)
73+
_blackHole(result)
74+
}
75+
76+
% else:
77+
78+
/// Always-failing conversion from ${Other}(${testValue}) to ${Self}.
79+
FixedPointConversionTraps.test("${Other}To${Self}Conversion/dest=${testValue}") {
80+
// Test that we check if we fail and crash when an integer would be truncated in conversion.
81+
let input = get${Other}(${testValue})
82+
expectCrashLater()
83+
var result = ${Self}(input)
84+
_blackHole(result)
85+
}
86+
87+
/// Always-nil failable conversion from ${Other}(${testValue}) to ${Self}.
88+
FixedPointConversionFailure.test("${Other}To${Self}Conversion/dest=${testValue}") {
89+
// Test that we check if we return nil when an integer would be truncated in conversion.
90+
let input = get${Other}(${testValue})
91+
var result = ${Self}(exactly: input)
92+
expectEqual(nil, result)
93+
_blackHole(result)
94+
}
95+
% end
96+
97+
% end # for testValue in ...
98+
% end # for in all_integer_types (Other)
99+
% end # for in all_integer_types (Self)
100+
""")
101+
102+
}%
103+
104+
#if arch(i386) || arch(arm)
105+
106+
${gyb.execute_template(
107+
int_to_int_conversion_template,
108+
word_bits=32)}
109+
110+
#elseif arch(x86_64) || arch(arm64) || arch(powerpc64) || arch(powerpc64le) || arch(s390x)
111+
112+
${gyb.execute_template(
113+
int_to_int_conversion_template,
114+
word_bits=64)}
115+
116+
#else
117+
118+
_UnimplementedError()
119+
120+
#endif
121+
122+
runAllTests()

0 commit comments

Comments
 (0)