Skip to content

Commit 48db876

Browse files
committed
Suppress the warning for inactive try/throw in do..catch
In the warning about having no try/throw within the body of a do..catch, replace the walk of the inactive clauses of IfConfigDecl with a syntactic check of inactive and unparsed regions to look for 'try' and 'throw' keywords. This both eliminates a dependency on IfConfigDecl and expands the usefulness of this warning suppression to unparsed code.
1 parent 4a4c0f6 commit 48db876

File tree

3 files changed

+111
-67
lines changed

3 files changed

+111
-67
lines changed

lib/ASTGen/Sources/ASTGen/CompilerBuildConfiguration.swift

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,51 @@ private struct InactiveCodeContainsReferenceCache {
341341
let configuredRegions: ConfiguredRegions
342342
}
343343

344+
/// Search in inactive/unparsed code to look for evidence that something that
345+
/// looks unused is actually used in some configurations.
346+
private enum InactiveCodeChecker {
347+
case name(String)
348+
case tryOrThrow
349+
350+
/// Search for the kind of entity in the given syntax node.
351+
func search(syntax: SourceFileSyntax, configuredRegions: ConfiguredRegions) -> Bool {
352+
// If there are no regions, everything is active. This is the common case.
353+
if configuredRegions.isEmpty {
354+
return false
355+
}
356+
357+
for token in syntax.tokens(viewMode: .sourceAccurate) {
358+
// Match what we're looking for, and continue iterating if it doesn't
359+
// match.
360+
switch self {
361+
case .name(let name):
362+
guard let identifier = token.identifier, identifier.name == name else {
363+
continue
364+
}
365+
366+
break
367+
368+
case .tryOrThrow:
369+
guard let keywordKind = token.keywordKind,
370+
keywordKind == .try || keywordKind == .throw else {
371+
continue
372+
}
373+
374+
break
375+
}
376+
377+
// We matched what we were looking for, now check whether it is in an
378+
// inactive or unparsed region.
379+
if configuredRegions.isActive(token) != .active {
380+
// Found it in a non-active region.
381+
return true
382+
}
383+
}
384+
385+
return false
386+
}
387+
}
388+
344389
/// Determine whether the inactive code within the given search range
345390
/// contains a token referring to the given name.
346391
@_cdecl("swift_ASTGen_inactiveCodeContainsReference")
@@ -378,26 +423,31 @@ public func inactiveCodeContainsReference(
378423
untypedCachePtr.pointee = .init(cache)
379424
}
380425

381-
// If there are no regions, everything is active. This is the common case.
382-
if configuredRegions.isEmpty {
383-
return false
384-
}
426+
return InactiveCodeChecker.name(String(bridged: bridgedName))
427+
.search(syntax: syntax, configuredRegions: configuredRegions)
428+
}
385429

386-
// Walk all of the tokens in the search range looking for one that references
387-
// the given name.
388-
let name = String(bridged: bridgedName)
389-
for token in syntax.tokens(viewMode: .sourceAccurate) {
390-
guard let identifier = token.identifier else {
391-
continue
392-
}
430+
@_cdecl("swift_ASTGen_inactiveCodeContainsTryOrThrow")
431+
public func inactiveCodeContainsTryOrThrow(
432+
astContext: BridgedASTContext,
433+
sourceFileBuffer: BridgedStringRef,
434+
searchRange: BridgedStringRef
435+
) -> Bool {
436+
// Parse the region.
393437

394-
if identifier.name == name && configuredRegions.isActive(token) != .active {
395-
// Found it in a non-active region.
396-
return true
397-
}
398-
}
438+
// FIXME: Use 'ExportedSourceFile' when C++ parser is replaced.
439+
let searchRangeBuffer = UnsafeBufferPointer<UInt8>(start: searchRange.data, count: searchRange.count)
440+
let syntax = Parser.parse(source: searchRangeBuffer)
441+
442+
// Compute the configured regions within the search range.
443+
let configuration = CompilerBuildConfiguration(
444+
ctx: astContext,
445+
sourceBuffer: searchRangeBuffer
446+
)
447+
let configuredRegions = syntax.configuredRegions(in: configuration)
399448

400-
return false
449+
return InactiveCodeChecker.tryOrThrow
450+
.search(syntax: syntax, configuredRegions: configuredRegions)
401451
}
402452

403453
@_cdecl("swift_ASTGen_freeInactiveCodeContainsReferenceCache")

lib/Sema/TypeCheckEffects.cpp

Lines changed: 37 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "TypeChecker.h"
1919
#include "TypeCheckConcurrency.h"
2020
#include "TypeCheckEffects.h"
21+
#include "swift/AST/ASTBridging.h"
2122
#include "swift/AST/ASTWalker.h"
2223
#include "swift/AST/DiagnosticsSema.h"
2324
#include "swift/AST/Effects.h"
@@ -522,9 +523,7 @@ class EffectsHandlingWalker : public ASTWalker {
522523
ShouldRecurse_t recurse = ShouldRecurse;
523524
// Skip the implementations of all local declarations... except
524525
// PBD. We should really just have a PatternBindingStmt.
525-
if (auto ic = dyn_cast<IfConfigDecl>(D)) {
526-
recurse = asImpl().checkIfConfig(ic);
527-
} else if (auto patternBinding = dyn_cast<PatternBindingDecl>(D)) {
526+
if (auto patternBinding = dyn_cast<PatternBindingDecl>(D)) {
528527
if (patternBinding->isAsyncLet())
529528
recurse = asImpl().checkAsyncLet(patternBinding);
530529
} else if (auto macroExpansionDecl = dyn_cast<MacroExpansionDecl>(D)) {
@@ -1717,10 +1716,6 @@ class ApplyClassifier {
17171716
return ShouldRecurse;
17181717
}
17191718

1720-
ShouldRecurse_t checkIfConfig(IfConfigDecl *D) {
1721-
return ShouldRecurse;
1722-
}
1723-
17241719
ShouldRecurse_t checkForEach(ForEachStmt *S) {
17251720
classification.merge(Self.classifyForEach(S));
17261721
return ShouldRecurse;
@@ -1832,10 +1827,6 @@ class ApplyClassifier {
18321827
return ShouldRecurse;
18331828
}
18341829

1835-
ShouldRecurse_t checkIfConfig(IfConfigDecl *D) {
1836-
return ShouldRecurse;
1837-
}
1838-
18391830
ShouldRecurse_t checkDoCatch(DoCatchStmt *S) {
18401831
return ShouldRecurse;
18411832
}
@@ -2719,6 +2710,14 @@ class Context {
27192710

27202711
};
27212712

2713+
/// ASTGen helper function to look for a "try" or "throw" in inactive code
2714+
/// within the given source file.
2715+
extern "C" bool swift_ASTGen_inactiveCodeContainsTryOrThrow(
2716+
BridgedASTContext ctx,
2717+
BridgedStringRef sourceFileBuffer,
2718+
BridgedStringRef searchRange
2719+
);
2720+
27222721
/// A class to walk over a local context and validate the correctness
27232722
/// of its error coverage.
27242723
class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage> {
@@ -3165,7 +3164,7 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
31653164

31663165
S->getBody()->walk(*this);
31673166

3168-
diagnoseNoThrowInDo(S, scope);
3167+
diagnoseNoThrowInDo(S, scope, S->getBody()->getSourceRange());
31693168

31703169
return MaxThrowingKind;
31713170
}
@@ -3189,17 +3188,40 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
31893188

31903189
S->getBody()->walk(*this);
31913190

3192-
diagnoseNoThrowInDo(S, scope);
3191+
diagnoseNoThrowInDo(S, scope, S->getBody()->getSourceRange());
31933192

31943193
scope.preserveCoverageFromNonExhaustiveCatch();
31953194
return MaxThrowingKind;
31963195
}
31973196

3198-
void diagnoseNoThrowInDo(DoCatchStmt *S, ContextScope &scope) {
3197+
/// Determine whether the inactive code within the given body range
3198+
/// contains a "try" or a "throw".
3199+
bool inactiveCodeContainsTryOrThrow(SourceRange bodyRange) {
3200+
#if SWIFT_BUILD_SWIFT_SYNTAX
3201+
SourceManager &sourceMgr = Ctx.SourceMgr;
3202+
auto bufferID = sourceMgr.findBufferContainingLoc(bodyRange.Start);
3203+
StringRef sourceFileText = sourceMgr.getEntireTextForBuffer(bufferID);
3204+
3205+
// Extract the search text from that buffer.
3206+
auto searchTextCharRange = Lexer::getCharSourceRangeFromSourceRange(
3207+
sourceMgr, bodyRange);
3208+
StringRef searchText = sourceMgr.extractText(searchTextCharRange, bufferID);
3209+
3210+
return swift_ASTGen_inactiveCodeContainsTryOrThrow(
3211+
Ctx, sourceFileText, searchText);
3212+
#else
3213+
return false;
3214+
#endif
3215+
}
3216+
3217+
void diagnoseNoThrowInDo(
3218+
DoCatchStmt *S, ContextScope &scope, SourceRange bodyRange
3219+
) {
31993220
// Warn if nothing threw within the body, unless this is the
32003221
// implicit do/catch in a debugger function.
32013222
if (!Flags.has(ContextFlags::HasAnyThrowSite) &&
3202-
!scope.wasTopLevelDebuggerFunction()) {
3223+
!scope.wasTopLevelDebuggerFunction() &&
3224+
!inactiveCodeContainsTryOrThrow(bodyRange)) {
32033225
Ctx.Diags.diagnose(S->getCatches().front()->getStartLoc(),
32043226
diag::no_throw_in_do_with_catch);
32053227
}
@@ -3318,41 +3340,6 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
33183340
return ShouldNotRecurse;
33193341
}
33203342

3321-
ShouldRecurse_t checkIfConfig(IfConfigDecl *ICD) {
3322-
// Check the inactive regions of a #if block to disable warnings that may
3323-
// be due to platform specific code.
3324-
struct ConservativeThrowChecker : public ASTWalker {
3325-
CheckEffectsCoverage &CEC;
3326-
ConservativeThrowChecker(CheckEffectsCoverage &CEC) : CEC(CEC) {}
3327-
3328-
MacroWalking getMacroWalkingBehavior() const override {
3329-
return MacroWalking::Arguments;
3330-
}
3331-
3332-
PostWalkResult<Expr *> walkToExprPost(Expr *E) override {
3333-
if (isa<TryExpr>(E))
3334-
CEC.Flags.set(ContextFlags::HasAnyThrowSite);
3335-
return Action::Continue(E);
3336-
}
3337-
3338-
PostWalkResult<Stmt *> walkToStmtPost(Stmt *S) override {
3339-
if (isa<ThrowStmt>(S))
3340-
CEC.Flags.set(ContextFlags::HasAnyThrowSite);
3341-
3342-
return Action::Continue(S);
3343-
}
3344-
};
3345-
3346-
for (auto &clause : ICD->getClauses()) {
3347-
// Active clauses are handled by the normal AST walk.
3348-
if (clause.isActive) continue;
3349-
3350-
for (auto elt : clause.Elements)
3351-
elt.walk(ConservativeThrowChecker(*this));
3352-
}
3353-
return ShouldRecurse;
3354-
}
3355-
33563343
ShouldRecurse_t checkThrow(ThrowStmt *S) {
33573344
if (auto classification = getApplyClassifier().classifyThrow(S)) {
33583345
Flags.set(ContextFlags::HasAnyThrowSite);

test/Parse/errors.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ func one() {
4949
} catch { // don't warn, #if code should be scanned.
5050
}
5151

52+
do {
53+
#if compiler(>=10)
54+
throw opaque_error()
55+
#endif
56+
} catch { // don't warn, #if code should be scanned.
57+
}
58+
5259
do {
5360
#if false
5461
throw opaque_error()

0 commit comments

Comments
 (0)