Simplify assertion machinery in the standard library.

This change includes a number of simplifications that allow us to
eliminate the type checker hack that specifically tries
AssertString. Doing so provides a 25% speedup in the
test/stdlib/ArrayNew.swift test (which is type-checker bound).

The specific simplifications here:
  - User-level
  assert/precondition/preconditionalFailure/assertionFailer/fatalError
  always take an autoclosure producing a String, eliminating the need
  for the StaticString/AssertString dance.
  - Standard-library internal _precondition/_sanityCheck/etc. always
  take a StaticString. When we want to improve the diagnostics in the
  standard library, we can provide a separate overload or
  differently-named function.
  - Remove AssertString, AssertStringType, StaticStringType, which are
  no longer used or needed
  - Remove the AssertString hack from the compiler
  - Remove the "BooleanType" overloads of these functions, because
  their usefuless left when we stopped making optional types conform
  to BooleanType (sorry, should have been a separate patch).



Swift SVN r22139
This commit is contained in:
Doug Gregor
2014-09-19 17:56:50 +00:00
parent 531f31130d
commit 7764f64cf8
10 changed files with 44 additions and 285 deletions

View File

@@ -284,14 +284,6 @@ getAlternativeLiteralTypes(KnownProtocolKind kind) {
case KnownProtocolKind::StringInterpolationConvertible: case KnownProtocolKind::StringInterpolationConvertible:
case KnownProtocolKind::StringLiteralConvertible: case KnownProtocolKind::StringLiteralConvertible:
case KnownProtocolKind::UnicodeScalarLiteralConvertible: case KnownProtocolKind::UnicodeScalarLiteralConvertible:
{
UnqualifiedLookup lookup(TC.Context.getIdentifier("AssertString"),
DC->getModuleScopeContext(),
nullptr);
if (auto typeDecl = lookup.getSingleTypeResult()) {
types.push_back(typeDecl->getDeclaredInterfaceType());
}
}
break; break;
case KnownProtocolKind::IntegerLiteralConvertible: case KnownProtocolKind::IntegerLiteralConvertible:

View File

@@ -16,25 +16,6 @@
/// release or fast mode these checks are disabled. This means they may have no /// release or fast mode these checks are disabled. This means they may have no
/// effect on program semantics, depending on the assert configuration. /// effect on program semantics, depending on the assert configuration.
% for (generic_params, closure_type, message_param, message_eval) in [
% ( '',
% 'Bool',
% '_ message: StaticString = StaticString()',
% 'message' ),
% ( '<Str : AssertStringType>',
% 'Bool',
% 'message: @autoclosure () -> Str',
% 'message().stringValue' ),
% ( '<T : BooleanType, Str : StaticStringType>',
% 'T',
% 'message: @autoclosure () -> Str',
% 'message().stringValue' ),
% ( '<T : BooleanType, Str : AssertStringType>',
% 'T',
% 'message: @autoclosure () -> Str',
% 'message().stringValue' ),
% ]:
/// Traditional C-style assert with an optional message. /// Traditional C-style assert with an optional message.
/// ///
/// When assertions are enabled and `condition` is false, stop program /// When assertions are enabled and `condition` is false, stop program
@@ -45,14 +26,15 @@
/// When assertions are turned off, the optimizer can assume that the /// When assertions are turned off, the optimizer can assume that the
/// `condition` is true. /// `condition` is true.
@transparent @transparent
public func assert${generic_params}( public func assert(
condition: @autoclosure () -> ${closure_type}, ${message_param}, condition: @autoclosure () -> Bool,
_ message: @autoclosure () -> String = String(),
file: StaticString = __FILE__, line: UWord = __LINE__ file: StaticString = __FILE__, line: UWord = __LINE__
) { ) {
// Only assert in debug mode. // Only assert in debug mode.
if _isDebugAssertConfiguration() { if _isDebugAssertConfiguration() {
if !_branchHint(condition(), true) { if !_branchHint(condition(), true) {
_assertionFailed("assertion failed", ${message_eval}, file, line) _assertionFailed("assertion failed", message(), file, line)
} }
} }
} }
@@ -63,14 +45,15 @@ public func assert${generic_params}(
/// ///
/// In unchecked mode the optimizer can assume that the `condition` is true. /// In unchecked mode the optimizer can assume that the `condition` is true.
@transparent @transparent
public func precondition${generic_params}( public func precondition(
condition: @autoclosure () -> ${closure_type}, ${message_param}, condition: @autoclosure () -> Bool,
_ message: @autoclosure () -> String = String(),
file: StaticString = __FILE__, line: UWord = __LINE__ file: StaticString = __FILE__, line: UWord = __LINE__
) { ) {
// Only check in debug and release mode. In release mode just trap. // Only check in debug and release mode. In release mode just trap.
if _isDebugAssertConfiguration() { if _isDebugAssertConfiguration() {
if !_branchHint(condition(), true) { if !_branchHint(condition(), true) {
_assertionFailed("precondition failed", ${message_eval}, file, line) _assertionFailed("precondition failed", message(), file, line)
} }
} else if _isReleaseAssertConfiguration() { } else if _isReleaseAssertConfiguration() {
let error = !condition() let error = !condition()
@@ -78,25 +61,15 @@ public func precondition${generic_params}(
} }
} }
% end
% for (generic_params, message_param, message_eval) in [
% ( '',
% 'message: StaticString',
% 'message' ),
% ( '<Str : AssertStringType>',
% 'message: Str',
% 'message.stringValue' ),
% ]:
/// A fatal error occurred and program execution should stop in debug mode. In /// A fatal error occurred and program execution should stop in debug mode. In
/// optimized builds this is a noop. /// optimized builds this is a noop.
@transparent @noreturn @transparent @noreturn
public func assertionFailure${generic_params}( public func assertionFailure(
${message_param}, file: StaticString = __FILE__, line: UWord = __LINE__ _ message: @autoclosure () -> String = String(),
file: StaticString = __FILE__, line: UWord = __LINE__
) { ) {
if _isDebugAssertConfiguration() { if _isDebugAssertConfiguration() {
_assertionFailed("fatal error", ${message_eval}, file, line) _assertionFailed("fatal error", message(), file, line)
} }
_conditionallyUnreachable() _conditionallyUnreachable()
} }
@@ -105,12 +78,13 @@ public func assertionFailure${generic_params}(
/// in optimized mode. In unchecked builds this is a noop, but the /// in optimized mode. In unchecked builds this is a noop, but the
/// optimizer can still assume that the call is unreachable. /// optimizer can still assume that the call is unreachable.
@transparent @noreturn @transparent @noreturn
public func preconditionFailure${generic_params}( public func preconditionFailure(
${message_param}, file: StaticString = __FILE__, line: UWord = __LINE__ _ message: @autoclosure () -> String = String(),
file: StaticString = __FILE__, line: UWord = __LINE__
) { ) {
// Only check in debug and release mode. In release mode just trap. // Only check in debug and release mode. In release mode just trap.
if _isDebugAssertConfiguration() { if _isDebugAssertConfiguration() {
_assertionFailed("fatal error", ${message_eval}, file, line) _assertionFailed("fatal error", message(), file, line)
} else if _isReleaseAssertConfiguration() { } else if _isReleaseAssertConfiguration() {
Builtin.int_trap() Builtin.int_trap()
} }
@@ -120,14 +94,13 @@ public func preconditionFailure${generic_params}(
/// A fatal error occurred and program execution should stop in debug, /// A fatal error occurred and program execution should stop in debug,
/// optimized and unchecked modes. /// optimized and unchecked modes.
@transparent @noreturn @transparent @noreturn
public func fatalError${generic_params}( public func fatalError(
${message_param}, file: StaticString = __FILE__, line: UWord = __LINE__ _ message: @autoclosure () -> String = String(),
file: StaticString = __FILE__, line: UWord = __LINE__
) { ) {
_assertionFailed("fatal error", ${message_eval}, file, line) _assertionFailed("fatal error", message(), file, line)
} }
% end
/// Library precondition checks /// Library precondition checks
/// ///
/// Library precondition checks are enabled in debug mode and release mode. When /// Library precondition checks are enabled in debug mode and release mode. When
@@ -149,21 +122,6 @@ public func _precondition(
Builtin.condfail(error.value) Builtin.condfail(error.value)
} }
} }
@transparent
public func _precondition<T : BooleanType>(
condition: @autoclosure () -> T, _ message: StaticString = StaticString(),
file: StaticString = __FILE__, line: UWord = __LINE__
) {
// Only check in debug and release mode. In release mode just trap.
if _isDebugAssertConfiguration() {
if !_branchHint(condition(), true) {
_fatalErrorMessage("fatal error", message, file, line)
}
} else if _isReleaseAssertConfiguration() {
let error = !condition().boolValue
Builtin.condfail(error.value)
}
}
@transparent @noreturn @transparent @noreturn
public func _preconditionFailure( public func _preconditionFailure(
@@ -215,19 +173,6 @@ public func _debugPrecondition(
} }
} }
@transparent
public func _debugPrecondition<T : BooleanType>(
condition: @autoclosure () -> T, _ message: StaticString = StaticString(),
file: StaticString = __FILE__, line: UWord = __LINE__
) {
// Only check in debug mode.
if _isDebugAssertConfiguration() {
if !_branchHint(condition(), true) {
_fatalErrorMessage("fatal error", message, file, line)
}
}
}
@transparent @noreturn @transparent @noreturn
public func _debugPreconditionFailure( public func _debugPreconditionFailure(
_ message: StaticString = StaticString(), _ message: StaticString = StaticString(),
@@ -256,18 +201,6 @@ public func _sanityCheck(
#endif #endif
} }
@transparent
public func _sanityCheck<T : BooleanType>(
condition: @autoclosure () -> T, _ message: StaticString = StaticString(),
file: StaticString = __FILE__, line: UWord = __LINE__
) {
#if INTERNAL_CHECKS_ENABLED
if !_branchHint(condition(), true) {
_fatalErrorMessage("fatal error", message, file, line)
}
#endif
}
@transparent @noreturn @transparent @noreturn
public func _sanityCheckFailure( public func _sanityCheckFailure(
_ message: StaticString = StaticString(), _ message: StaticString = StaticString(),

View File

@@ -10,41 +10,22 @@
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// Instances of conforming types can be created from string literals
/// and are used in assertions and precondition checks.
///
/// This protocol exists only for directing overload resolution and
/// string literal type deduction in assertions.
///
/// See also: `StaticStringType`, `StaticString`, `AssertString`
public protocol AssertStringType
: UnicodeScalarLiteralConvertible,
ExtendedGraphemeClusterLiteralConvertible,
StringLiteralConvertible {
/// The textual value, as a `String`
var stringValue: String { get }
}
/// This protocol exists only for directing overload resolution and
/// string literal type deduction in assertions.
///
/// See also: `AssertStringType`, `StaticString`, `AssertString`
public protocol StaticStringType : AssertStringType {}
// Implementation Note: Because StaticString is used in the // Implementation Note: Because StaticString is used in the
// implementation of assert() and fatal(), we keep it extremely close // implementation of _precondition(), _fatalErrorMessage(), etc., we
// to the bare metal. In particular, because we store only Builtin // keep it extremely close to the bare metal. In particular, because
// types, we are guaranteed that no assertions are involved in its // we store only Builtin types, we are guaranteed that no assertions
// construction. This feature is crucial for preventing infinite // are involved in its construction. This feature is crucial for
// recursion even in non-asserting cases. // preventing infinite recursion even in non-asserting cases.
/// An extremely simple string designed to represent something /// An extremely simple string designed to represent something
/// "statically knowable". /// "statically knowable".
public struct StaticString public struct StaticString
: StaticStringType, : _BuiltinUnicodeScalarLiteralConvertible,
_BuiltinUnicodeScalarLiteralConvertible,
_BuiltinExtendedGraphemeClusterLiteralConvertible, _BuiltinExtendedGraphemeClusterLiteralConvertible,
_BuiltinStringLiteralConvertible, _BuiltinStringLiteralConvertible,
UnicodeScalarLiteralConvertible,
ExtendedGraphemeClusterLiteralConvertible,
StringLiteralConvertible,
Printable, Printable,
DebugPrintable { DebugPrintable {
@@ -202,65 +183,3 @@ public struct StaticString
return self.stringValue.debugDescription return self.stringValue.debugDescription
} }
} }
/// A String-like type that can be constructed from string interpolation, and
/// is considered less specific than `StaticString` in overload resolution.
public struct AssertString
: AssertStringType, StringInterpolationConvertible, Printable,
DebugPrintable {
public var stringValue: String
@transparent
public init() {
self.stringValue = ""
}
@transparent
public init(_ value: String) {
self.stringValue = value
}
@effects(readonly)
@transparent
public init(unicodeScalarLiteral value: String) {
self.stringValue = value
}
@effects(readonly)
@transparent
public init(extendedGraphemeClusterLiteral value: String) {
self.stringValue = value
}
@effects(readonly)
@transparent
public init(stringLiteral value: String) {
self.stringValue = value
}
public static func convertFromStringInterpolation(
strings: AssertString...
) -> AssertString {
var result = String()
for str in strings {
result += str.stringValue
}
return AssertString(result)
}
@transparent
public static func convertFromStringInterpolationSegment<T>(
expr: T
) -> AssertString {
return AssertString(String.convertFromStringInterpolationSegment(expr))
}
public var description: String {
return self.stringValue
}
public var debugDescription: String {
return self.stringValue.debugDescription
}
}

View File

@@ -458,7 +458,12 @@ extension String : CollectionType {
@availability(*, unavailable, message="cannot subscript String with an Int") @availability(*, unavailable, message="cannot subscript String with an Int")
public subscript(i: Int) -> Character { public subscript(i: Int) -> Character {
fatalError("cannot subscript String with an Int") _fatalErrorMessage(
"fatal error",
"cannot subscript String with an Int",
__FILE__,
__LINE__
)
} }
public func generate() -> IndexingGenerator<String> { public func generate() -> IndexingGenerator<String> {

View File

@@ -275,4 +275,4 @@ postfix func <*> (_: Test<True>) -> String? { return .None }
class Test<C: Bool_> : MetaFunction {} // picks first <*> class Test<C: Bool_> : MetaFunction {} // picks first <*>
typealias Inty = Test<True>.Result typealias Inty = Test<True>.Result
var iy : Inty = 5 // okay, because we picked the first <*> var iy : Inty = 5 // okay, because we picked the first <*>
var iy2 : Inty = "hello" // expected-error{{type 'Inty' does not conform to protocol 'StringLiteralConvertible'}} var iy2 : Inty = "hello" // expected-error{{'String' is not convertible to 'Inty'}}

View File

@@ -173,7 +173,7 @@ enum RawTypeWithCharacterValues : Character {
} }
enum RawTypeWithCharacterValues_Error1 : Character { enum RawTypeWithCharacterValues_Error1 : Character {
case First = "abc" // expected-error {{'Character' does not conform to protocol 'StringLiteralConvertible'}} case First = "abc" // expected-error {{'String' is not convertible to 'Character'}}
} }
enum RawTypeWithFloatValues : Float { enum RawTypeWithFloatValues : Float {

View File

@@ -150,7 +150,7 @@ func derivedType() {
// Referencing a nonexistent member or constructor should not trigger errors // Referencing a nonexistent member or constructor should not trigger errors
// about the type expression. // about the type expression.
func nonexistentMember() { func nonexistentMember() {
let cons = Foo("this constructor does not exist") // expected-error{{type '()' does not conform to protocol 'StringLiteralConvertible'}} let cons = Foo("this constructor does not exist") // expected-error{{expression does not conform to type 'StringLiteralConvertible'}}
let prop = Foo.nonexistent // expected-error{{does not have a member named 'nonexistent'}} let prop = Foo.nonexistent // expected-error{{does not have a member named 'nonexistent'}}
let meth = Foo.nonexistent() // expected-error{{does not have a member named 'nonexistent'}} let meth = Foo.nonexistent() // expected-error{{does not have a member named 'nonexistent'}}
} }

View File

@@ -512,7 +512,7 @@ struct Rule {
} }
var ruleVar: Rule var ruleVar: Rule
ruleVar = Rule("a") // expected-error {{type '(target: String, dependencies: String)' does not conform to protocol 'UnicodeScalarLiteralConvertible'}} ruleVar = Rule("a") // expected-error {{missing argument for parameter 'dependencies' in call}}
class C { class C {

View File

@@ -8,17 +8,17 @@ func testTypeInference() {
var singleQuoted1 = 'a' // expected-error {{expression does not conform to type 'CharacterLiteralConvertible'}} var singleQuoted1 = 'a' // expected-error {{expression does not conform to type 'CharacterLiteralConvertible'}}
var singleQuoted2 = '' // expected-error {{expression does not conform to type 'CharacterLiteralConvertible'}} var singleQuoted2 = '' // expected-error {{expression does not conform to type 'CharacterLiteralConvertible'}}
var us1: UnicodeScalar = "" // expected-error {{type 'UnicodeScalar' does not conform to protocol 'StringLiteralConvertible'}} var us1: UnicodeScalar = "" // expected-error {{'String' is not convertible to 'UnicodeScalar'}}
var us2: UnicodeScalar = "a" var us2: UnicodeScalar = "a"
isUnicodeScalar(&us2) isUnicodeScalar(&us2)
var us3: UnicodeScalar = "" var us3: UnicodeScalar = ""
isUnicodeScalar(&us3) isUnicodeScalar(&us3)
var us4: UnicodeScalar = "" var us4: UnicodeScalar = ""
isUnicodeScalar(&us4) isUnicodeScalar(&us4)
var us5: UnicodeScalar = "\u{304b}\u{3099}" // expected-error {{type 'UnicodeScalar' does not conform to protocol 'ExtendedGraphemeClusterLiteralConvertible'}} var us5: UnicodeScalar = "\u{304b}\u{3099}" // expected-error {{'String' is not convertible to 'UnicodeScalar'}}
var us6: UnicodeScalar = "ab" // expected-error {{type 'UnicodeScalar' does not conform to protocol 'StringLiteralConvertible'}} var us6: UnicodeScalar = "ab" // expected-error {{'String' is not convertible to 'UnicodeScalar'}}
var ch1: Character = "" // expected-error {{type 'Character' does not conform to protocol 'StringLiteralConvertible'}} var ch1: Character = "" // expected-error {{'String' is not convertible to 'Character'}}
var ch2: Character = "a" var ch2: Character = "a"
isCharacter(&ch2) isCharacter(&ch2)
var ch3: Character = "" var ch3: Character = ""
@@ -27,7 +27,7 @@ func testTypeInference() {
isCharacter(&ch4) isCharacter(&ch4)
var ch5: Character = "\u{304b}\u{3099}" var ch5: Character = "\u{304b}\u{3099}"
isCharacter(&ch5) isCharacter(&ch5)
var ch6: Character = "ab" // expected-error {{type 'Character' does not conform to protocol 'StringLiteralConvertible'}} var ch6: Character = "ab" // expected-error {{'String' is not convertible to 'Character'}}
var s1 = "" var s1 = ""
isString(&s1) isString(&s1)

View File

@@ -14,15 +14,6 @@ import StdlibUnittest
// Utilities. // Utilities.
//===--- //===---
struct Truthiness : BooleanType {
init(_ value: Bool) { self.value = value }
var boolValue: Bool { return value }
var value: Bool
}
var falsie = Truthiness(false)
var truthie = Truthiness(true)
func isDebugOrRelease() -> Bool { func isDebugOrRelease() -> Bool {
return !_isFastAssertConfiguration() return !_isFastAssertConfiguration()
} }
@@ -80,31 +71,6 @@ Assert.test("assert/StringInterpolation")
assert(x == 42, "this \(should) fail") assert(x == 42, "this \(should) fail")
} }
Assert.test("assert/BooleanType")
.xfail(.Custom(
{ !_isDebugAssertConfiguration() },
reason: "assertions are disabled in Release and Unchecked mode"))
.crashOutputMatches("this should fail")
.code {
assert(truthie, "should not fail")
expectCrashLater()
assert(falsie, "this should fail")
}
Assert.test("assert/BooleanType/StringInterpolation")
.xfail(.Custom(
{ !_isDebugAssertConfiguration() },
reason: "assertions are disabled in Release and Unchecked mode"))
.crashOutputMatches("this should fail")
.code {
var should = "should"
assert(truthie, "\(should) not fail")
expectCrashLater()
assert(falsie, "this \(should) fail")
}
Assert.test("assertionFailure") Assert.test("assertionFailure")
.skip(.Custom( .skip(.Custom(
{ !_isDebugAssertConfiguration() }, { !_isDebugAssertConfiguration() },
@@ -151,29 +117,6 @@ Assert.test("precondition/StringInterpolation")
precondition(x == 42, "this \(should) fail") precondition(x == 42, "this \(should) fail")
} }
Assert.test("precondition/BooleanType")
.xfail(.Custom(
{ _isFastAssertConfiguration() },
reason: "preconditions are disabled in Unchecked mode"))
.crashOutputMatches(_isDebugAssertConfiguration() ? "this should fail" : "")
.code {
precondition(truthie, "should not fail")
expectCrashLater()
precondition(falsie, "this should fail")
}
Assert.test("precondition/BooleanType/StringInterpolation")
.xfail(.Custom(
{ _isFastAssertConfiguration() },
reason: "preconditions are disabled in Unchecked mode"))
.crashOutputMatches(_isDebugAssertConfiguration() ? "this should fail" : "")
.code {
var should = "should"
precondition(truthie, "\(should) not fail")
expectCrashLater()
precondition(falsie, "this \(should) fail")
}
Assert.test("preconditionFailure") Assert.test("preconditionFailure")
.skip(.Custom( .skip(.Custom(
{ _isFastAssertConfiguration() }, { _isFastAssertConfiguration() },
@@ -222,17 +165,6 @@ Assert.test("_precondition")
_precondition(x == 42, "this should fail") _precondition(x == 42, "this should fail")
} }
Assert.test("_precondition/BooleanType")
.xfail(.Custom(
{ _isFastAssertConfiguration() },
reason: "preconditions are disabled in Unchecked mode"))
.crashOutputMatches(_isDebugAssertConfiguration() ? "this should fail" : "")
.code {
_precondition(truthie, "should not fail")
expectCrashLater()
_precondition(falsie, "this should fail")
}
Assert.test("_preconditionFailure") Assert.test("_preconditionFailure")
.skip(.Custom( .skip(.Custom(
{ _isFastAssertConfiguration() }, { _isFastAssertConfiguration() },
@@ -255,17 +187,6 @@ Assert.test("_debugPrecondition")
_debugPrecondition(x == 42, "this should fail") _debugPrecondition(x == 42, "this should fail")
} }
Assert.test("_debugPrecondition/BooleanType")
.xfail(.Custom(
{ !_isDebugAssertConfiguration() },
reason: "debug preconditions are disabled in Release and Unchecked mode"))
.crashOutputMatches(_isDebugAssertConfiguration() ? "this should fail" : "")
.code {
_debugPrecondition(truthie, "should not fail")
expectCrashLater()
_debugPrecondition(falsie, "this should fail")
}
Assert.test("_debugPreconditionFailure") Assert.test("_debugPreconditionFailure")
.skip(.Custom( .skip(.Custom(
{ !_isDebugAssertConfiguration() }, { !_isDebugAssertConfiguration() },
@@ -288,17 +209,6 @@ Assert.test("_sanityCheck")
_sanityCheck(x == 42, "this should fail") _sanityCheck(x == 42, "this should fail")
} }
Assert.test("_sanityCheck/BooleanType")
.xfail(.Custom(
{ !_isStdlibInternalChecksEnabled() },
reason: "sanity checks are disabled in this build of stdlib"))
.crashOutputMatches("this should fail")
.code {
_sanityCheck(truthie, "should not fail")
expectCrashLater()
_sanityCheck(falsie, "this should fail")
}
Assert.test("_sanityCheckFailure") Assert.test("_sanityCheckFailure")
.skip(.Custom( .skip(.Custom(
{ !_isStdlibInternalChecksEnabled() }, { !_isStdlibInternalChecksEnabled() },