Skip to content

Commit ec850fd

Browse files
authored
Merge pull request #59746 from hamishknight/importance
[Sema] Add fix-it to import RegexBuilder
2 parents 317a6ab + c56ea46 commit ec850fd

9 files changed

+182
-0
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4867,6 +4867,9 @@ ERROR(regex_capture_types_failed_to_decode,none,
48674867
"failed to decode capture types for regular expression literal; this may "
48684868
"be a compiler bug", ())
48694869

4870+
ERROR(must_import_regex_builder_module,none,
4871+
"regex builder requires the 'RegexBuilder' module be imported'", ())
4872+
48704873
//------------------------------------------------------------------------------
48714874
// MARK: Type Check Types
48724875
//------------------------------------------------------------------------------

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ IDENTIFIER(Regex)
258258
IDENTIFIER_(regexString)
259259
IDENTIFIER(version)
260260
IDENTIFIER_(StringProcessing)
261+
IDENTIFIER(RegexBuilder)
261262

262263
// Distributed actors
263264
IDENTIFIER(ActorID)

lib/Sema/CSDiagnostics.cpp

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "swift/AST/Expr.h"
2727
#include "swift/AST/GenericSignature.h"
2828
#include "swift/AST/Initializer.h"
29+
#include "swift/AST/ImportCache.h"
2930
#include "swift/AST/ParameterList.h"
3031
#include "swift/AST/Pattern.h"
3132
#include "swift/AST/ProtocolConformance.h"
@@ -6486,6 +6487,9 @@ bool ArgumentMismatchFailure::diagnoseAsError() {
64866487
if (diagnosePropertyWrapperMismatch())
64876488
return true;
64886489

6490+
if (diagnoseAttemptedRegexBuilder())
6491+
return true;
6492+
64896493
if (diagnoseTrailingClosureMismatch())
64906494
return true;
64916495

@@ -6730,6 +6734,91 @@ bool ArgumentMismatchFailure::diagnosePropertyWrapperMismatch() const {
67306734
return true;
67316735
}
67326736

6737+
/// Add a fix-it to insert an import of a module.
6738+
static void fixItImport(InFlightDiagnostic &diag, Identifier moduleName,
6739+
DeclContext *dc) {
6740+
auto *SF = dc->getParentSourceFile();
6741+
if (!SF)
6742+
return;
6743+
6744+
SourceLoc insertLoc;
6745+
bool isTrailing = true;
6746+
6747+
// Check if we can insert as the last import statement.
6748+
auto decls = SF->getTopLevelDecls();
6749+
for (auto *decl : decls) {
6750+
auto *importDecl = dyn_cast<ImportDecl>(decl);
6751+
if (!importDecl) {
6752+
if (insertLoc.isValid())
6753+
break;
6754+
continue;
6755+
}
6756+
insertLoc = importDecl->getEndLoc();
6757+
}
6758+
6759+
// If not, insert it as the first decl with a valid source location.
6760+
if (insertLoc.isInvalid()) {
6761+
for (auto *decl : decls) {
6762+
if (auto loc = decl->getStartLoc()) {
6763+
insertLoc = loc;
6764+
isTrailing = false;
6765+
break;
6766+
}
6767+
}
6768+
}
6769+
6770+
// If we didn't resolve to a valid location, give up.
6771+
if (insertLoc.isInvalid())
6772+
return;
6773+
6774+
SmallString<32> insertText;
6775+
if (isTrailing) {
6776+
insertText.append("\n");
6777+
}
6778+
insertText.append("import ");
6779+
insertText.append(moduleName.str());
6780+
if (isTrailing) {
6781+
diag.fixItInsertAfter(insertLoc, insertText);
6782+
} else {
6783+
insertText.append("\n\n");
6784+
diag.fixItInsert(insertLoc, insertText);
6785+
}
6786+
}
6787+
6788+
bool ArgumentMismatchFailure::diagnoseAttemptedRegexBuilder() const {
6789+
auto &ctx = getASTContext();
6790+
6791+
// Should be a lone trailing closure argument.
6792+
if (!Info.isTrailingClosure() || !Info.getArgList()->isUnary())
6793+
return false;
6794+
6795+
// Check if this an application of a Regex initializer, and the user has not
6796+
// imported RegexBuilder.
6797+
auto *ctor = dyn_cast_or_null<ConstructorDecl>(getCallee());
6798+
if (!ctor)
6799+
return false;
6800+
6801+
auto *ctorDC = ctor->getInnermostTypeContext();
6802+
if (!ctorDC || ctorDC->getSelfNominalTypeDecl() != ctx.getRegexDecl())
6803+
return false;
6804+
6805+
// If the RegexBuilder module is loaded, make sure it hasn't been imported.
6806+
// Note this will cause us to diagnose even if another SourceFile has
6807+
// imported RegexBuilder, and its extensions have leaked into this file. This
6808+
// is a longstanding lookup bug, and it's probably a good idea to suggest
6809+
// explicitly importing RegexBuilder regardless in that case.
6810+
if (auto *regexBuilderModule = ctx.getLoadedModule(ctx.Id_RegexBuilder)) {
6811+
auto &importCache = getASTContext().getImportCache();
6812+
if (importCache.isImportedBy(regexBuilderModule, getDC()))
6813+
return false;
6814+
}
6815+
6816+
// Suggest importing RegexBuilder.
6817+
auto diag = emitDiagnostic(diag::must_import_regex_builder_module);
6818+
fixItImport(diag, ctx.Id_RegexBuilder, getDC());
6819+
return true;
6820+
}
6821+
67336822
bool ArgumentMismatchFailure::diagnoseTrailingClosureMismatch() const {
67346823
if (!Info.isTrailingClosure())
67356824
return false;

lib/Sema/CSDiagnostics.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1997,6 +1997,10 @@ class ArgumentMismatchFailure : public ContextualFailure {
19971997
/// so we have to attend to that in diagnostics.
19981998
bool diagnoseMisplacedMissingArgument() const;
19991999

2000+
/// Diagnose an attempt to pass a trailing closure to a Regex initializer
2001+
/// without importing RegexBuilder.
2002+
bool diagnoseAttemptedRegexBuilder() const;
2003+
20002004
protected:
20012005
/// \returns The position of the argument being diagnosed, starting at 1.
20022006
unsigned getArgPosition() const { return Info.getArgPosition(); }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// RUN: %target-typecheck-verify-swift -enable-bare-slash-regex -disable-availability-checking
2+
3+
import RegexBuilder
4+
5+
extension Regex where Output == Substring {
6+
init(_ x: String) {} // expected-note {{'init(_:)' declared here}}
7+
}
8+
9+
func foo() {
10+
// FIXME: This diagnostic could probably be better, it's not clear we should
11+
// be resolving to init(_ x: String) vs the result builder API and diagnosing
12+
// the fact that Int isn't a RegexComponent.
13+
_ = Regex { // expected-error {{trailing closure passed to parameter of type 'String' that does not accept a closure}}
14+
0
15+
}
16+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// RUN: %empty-directory(%t)
2+
3+
// Generate some dummy modules to import
4+
// RUN: %target-swift-frontend -emit-module -module-name A -o %t/A.swiftmodule %S/Inputs/dummy.swift
5+
// RUN: %target-swift-frontend -emit-module -module-name B -o %t/B.swiftmodule %S/Inputs/dummy.swift
6+
// RUN: %target-swift-frontend -emit-module -module-name C -o %t/C.swiftmodule %S/Inputs/dummy.swift
7+
8+
// RUN: %target-typecheck-verify-swift -enable-bare-slash-regex -disable-availability-checking -I %t
9+
10+
// REQUIRES: swift_in_compiler
11+
12+
import A
13+
14+
import B
15+
16+
Regex { // expected-error {{regex builder requires the 'RegexBuilder' module be imported'}} {{14:9-9=\nimport RegexBuilder}}
17+
/abc/
18+
}
19+
20+
import C
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// RUN: %target-typecheck-verify-swift -enable-bare-slash-regex -disable-availability-checking
2+
3+
// REQUIRES: swift_in_compiler
4+
5+
struct S {
6+
func foo() {
7+
Regex { // expected-error {{regex builder requires the 'RegexBuilder' module be imported'}} {{5:1-1=import RegexBuilder\n\n}}
8+
/abc/
9+
}
10+
}
11+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// RUN: %target-typecheck-verify-swift -enable-bare-slash-regex -disable-availability-checking
2+
3+
// REQUIRES: swift_in_compiler
4+
5+
Regex {} // expected-error {{regex builder requires the 'RegexBuilder' module be imported'}} {{5:1-1=import RegexBuilder\n\n}}
6+
7+
Regex { // expected-error {{regex builder requires the 'RegexBuilder' module be imported'}} {{5:1-1=import RegexBuilder\n\n}}
8+
/abc/
9+
}
10+
11+
Regex { // expected-error {{regex builder requires the 'RegexBuilder' module be imported'}} {{5:1-1=import RegexBuilder\n\n}}
12+
/abc/
13+
/def/
14+
}
15+
16+
Regex { // expected-error {{regex builder requires the 'RegexBuilder' module be imported'}} {{5:1-1=import RegexBuilder\n\n}}
17+
/abc/
18+
"def"
19+
/g(h)(i)/
20+
}
21+
22+
// FIXME: Unfortunately we bail from CSGen if we end up with an ErrorExpr, so
23+
// don't get a chance to diagnose. We ought to try solving with holes.
24+
// For now at least, this error should at least hopefully nudge users into
25+
// realizing they have a missing import.
26+
Regex {
27+
Capture { // expected-error {{cannot find 'Capture' in scope}}
28+
/abc/
29+
}
30+
}
31+
32+
// It's not clear what the user is doing here so fall back to regular diagnostics.
33+
Regex(a: 0) {
34+
// expected-error@-1 {{cannot convert value of type 'Int' to expected argument type 'String'}}
35+
// expected-error@-2 {{extra trailing closure passed in call}}
36+
/abc/
37+
}

0 commit comments

Comments
 (0)