From 7cad6cb5547b1325967652dacf2d8536603b4172 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 5 Jul 2022 11:25:44 -0700 Subject: [PATCH] Throw error when Output is wrong type better diagnostic --- Sources/_StringProcessing/Compiler.swift | 4 ++++ .../_StringProcessing/Regex/AnyRegexOutput.swift | 16 ++++++++++++++-- .../Utility/TypeVerification.swift | 11 ++++++----- .../RegexBuilderTests/AnyRegexOutputTests.swift | 11 +++++++++++ 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/Sources/_StringProcessing/Compiler.swift b/Sources/_StringProcessing/Compiler.swift index f47898e4e..530126a32 100644 --- a/Sources/_StringProcessing/Compiler.swift +++ b/Sources/_StringProcessing/Compiler.swift @@ -47,10 +47,14 @@ enum RegexCompilationError: Error, CustomStringConvertible { // TODO: Source location? case uncapturedReference + case incorrectOutputType(incorrect: Any.Type, correct: Any.Type) + var description: String { switch self { case .uncapturedReference: return "Found a reference used before it captured any match." + case .incorrectOutputType(let incorrect, let correct): + return "Cast to incorrect type 'Regex<\(incorrect)>', expected 'Regex<\(correct)>'" } } } diff --git a/Sources/_StringProcessing/Regex/AnyRegexOutput.swift b/Sources/_StringProcessing/Regex/AnyRegexOutput.swift index 20731ad39..0cf27af6b 100644 --- a/Sources/_StringProcessing/Regex/AnyRegexOutput.swift +++ b/Sources/_StringProcessing/Regex/AnyRegexOutput.swift @@ -161,7 +161,18 @@ extension Regex { _ pattern: String, as: Output.Type = Output.self ) throws { - self.init(ast: try parse(pattern, .traditional)) + let regex = Regex(ast: try parse(pattern, .traditional)) + + let (isSuccess, correctType) = regex._verifyType() + + guard isSuccess else { + throw RegexCompilationError.incorrectOutputType( + incorrect: Output.self, + correct: correctType + ) + } + + self = regex } /// Produces a regex that matches `verbatim` exactly, as though every @@ -217,7 +228,8 @@ extension Regex { as: Output.Type = Output.self ) { self.init(node: erased.root) - guard self._verifyType() else { + + guard _verifyType().0 else { return nil } } diff --git a/Sources/_StringProcessing/Utility/TypeVerification.swift b/Sources/_StringProcessing/Utility/TypeVerification.swift index c3aa53c7a..c5043bf97 100644 --- a/Sources/_StringProcessing/Utility/TypeVerification.swift +++ b/Sources/_StringProcessing/Utility/TypeVerification.swift @@ -13,16 +13,16 @@ @available(SwiftStdlib 5.7, *) extension Regex { - internal func _verifyType() -> Bool { + internal func _verifyType() -> (Bool, Any.Type) { guard Output.self != AnyRegexOutput.self else { - return true + return (true, Output.self) } var tupleElements: [Any.Type] = [] var labels = "" for capture in program.tree.captureList.captures { - var captureType: Any.Type = capture.type ?? Substring.self + var captureType = capture.type var i = capture.optionalDepth while i != 0 { @@ -41,7 +41,8 @@ extension Regex { // If we have no captures, then our Regex must be Regex. if tupleElements.count == 1 { - return Output.self == program.tree.root.wholeMatchType + let wholeMatchType = program.tree.root.wholeMatchType + return (Output.self == wholeMatchType, wholeMatchType) } let createdType = TypeConstruction.tupleType( @@ -52,6 +53,6 @@ extension Regex { labels: labels.all { $0 == " " } ? nil : labels ) - return Output.self == createdType + return (Output.self == createdType, createdType) } } diff --git a/Tests/RegexBuilderTests/AnyRegexOutputTests.swift b/Tests/RegexBuilderTests/AnyRegexOutputTests.swift index d628ce0d9..e6c3214b9 100644 --- a/Tests/RegexBuilderTests/AnyRegexOutputTests.swift +++ b/Tests/RegexBuilderTests/AnyRegexOutputTests.swift @@ -218,6 +218,17 @@ extension RegexDSLTests { noteOutput ) + // Run-time strings (errors) + XCTAssertThrowsError(try Regex("abc", as: (Substring, Substring).self)) + XCTAssertThrowsError(try Regex("(abc)", as: Substring.self)) + XCTAssertThrowsError(try Regex("(?abc)", as: (Substring, Substring).self)) + XCTAssertThrowsError(try Regex("(?abc)?", as: (Substring, test: Substring).self)) + + XCTAssertNoThrow(try Regex("abc", as: Substring.self)) + XCTAssertNoThrow(try Regex("(abc)", as: (Substring, Substring).self)) + XCTAssertNoThrow(try Regex("(?abc)", as: (Substring, test: Substring).self)) + XCTAssertNoThrow(try Regex("(?abc)?", as: (Substring, test: Substring?).self)) + // Builders check( Regex {