Skip to content

Commit b96a107

Browse files
committed
[NFC] Add pure test of pretty-printed cycle errors
This commit implements a facility to artificially cause a request cycle involving various types so we can test how circularity errors print without having to carefully craft test cases where they happen.
1 parent 22c3101 commit b96a107

File tree

7 files changed

+121
-1
lines changed

7 files changed

+121
-1
lines changed

include/swift/AST/TypeCheckRequests.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2956,6 +2956,31 @@ class TypeCheckCompletionHandlerAsyncAttrRequest
29562956
bool isCached() const { return true; }
29572957
};
29582958

2959+
/// Perform a series of recursive calls with the \c first and \c rest decls that
2960+
/// will eventually cause a cycle in the request stack.
2961+
///
2962+
/// This request is used to test diag::circular_reference and
2963+
/// diag::circular_reference_through.
2964+
class DebugIntentionallyCauseCycleRequest
2965+
: public SimpleRequest<
2966+
DebugIntentionallyCauseCycleRequest,
2967+
evaluator::SideEffect(Decl *, ArrayRef<Decl *>),
2968+
RequestFlags::Cached> {
2969+
public:
2970+
using SimpleRequest::SimpleRequest;
2971+
2972+
private:
2973+
friend SimpleRequest;
2974+
2975+
// Evaluation.
2976+
evaluator::SideEffect
2977+
evaluate(Evaluator &evaluator, Decl *first, ArrayRef<Decl *> rest) const;
2978+
2979+
public:
2980+
// Caching
2981+
bool isCached() const { return true; }
2982+
};
2983+
29592984
void simple_display(llvm::raw_ostream &out, Type value);
29602985
void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR);
29612986
void simple_display(llvm::raw_ostream &out, ImplicitMemberAction action);

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,3 +326,6 @@ SWIFT_REQUEST(TypeChecker, GetImplicitSendableRequest,
326326
SWIFT_REQUEST(TypeChecker, TypeCheckCompletionHandlerAsyncAttrRequest,
327327
bool (AbstractFunctionDecl *, CompletionHandlerAsyncAttr *),
328328
Cached, NoLocationInfo)
329+
SWIFT_REQUEST(TypeChecker, DebugIntentionallyCauseCycleRequest,
330+
evaluator::SideEffect(Decl *, ArrayRef<Decl *>), Cached,
331+
NoLocationInfo)

include/swift/Basic/LangOptions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,10 @@ namespace swift {
572572

573573
/// See \ref FrontendOptions.PrintFullConvention
574574
bool PrintFullConvention = false;
575+
576+
/// Deliberately create a request cycle involving the declarations for the
577+
/// types named here. Used to test the request evaluator's diagnostics.
578+
std::vector<std::string> DebugCauseCycleViaDeclNames;
575579
};
576580

577581
/// Options for controlling the behavior of the Clang importer.

include/swift/Option/FrontendOptions.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,8 @@ def debug_emit_invalid_swiftinterface_syntax : Flag<["-"], "debug-emit-invalid-s
271271

272272
def debug_cycles : Flag<["-"], "debug-cycles">,
273273
HelpText<"Print out debug dumps when cycles are detected in evaluation">;
274+
def debug_cause_cycle_via_decl : Separate<["-"], "debug-cause-cycle-via-decl">,
275+
HelpText<"Intentially creates a request cycle involving each of the declarations passed with this flag">;
274276

275277
def debug_time_function_bodies : Flag<["-"], "debug-time-function-bodies">,
276278
HelpText<"Dumps the time it takes to type-check each function body">;

lib/Frontend/CompilerInvocation.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,10 @@ static bool ParseTypeCheckerArgs(TypeCheckerOptions &Opts, ArgList &Args,
823823
Opts.DebugForbidTypecheckPrefix = A->getValue();
824824
}
825825

826+
for (const Arg *A : Args.filtered(OPT_debug_cause_cycle_via_decl)) {
827+
Opts.DebugCauseCycleViaDeclNames.push_back(A->getValue());
828+
}
829+
826830
if (Args.getLastArg(OPT_solver_disable_shrink))
827831
Opts.SolverDisableShrink = true;
828832

lib/Sema/TypeChecker.cpp

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,13 +350,63 @@ void swift::performWholeModuleTypeChecking(SourceFile &SF) {
350350
diagnoseObjCMethodConflicts(SF);
351351
diagnoseObjCUnsatisfiedOptReqConflicts(SF);
352352
diagnoseUnintendedObjCMethodOverrides(SF);
353-
return;
353+
break;
354354
case SourceFileKind::SIL:
355355
case SourceFileKind::Interface:
356356
// SIL modules and .swiftinterface files don't benefit from whole-module
357357
// ObjC checking - skip it.
358+
break;
359+
}
360+
361+
// If requested, create an artificial cycle to diagnose an error at.
362+
const auto &cycleViaDeclNames =
363+
Ctx.TypeCheckerOpts.DebugCauseCycleViaDeclNames;
364+
if (cycleViaDeclNames.empty())
358365
return;
366+
367+
SmallVector<Decl *, 4> cycleViaDecls;
368+
for (const auto &fullName : cycleViaDeclNames) {
369+
SmallVector<ComponentIdentTypeRepr *, 4> components;
370+
StringRef rest{fullName};
371+
do {
372+
StringRef first;
373+
std::tie(first, rest) = rest.split('.');
374+
375+
auto firstIdent = Ctx.getIdentifier(first);
376+
components.push_back(
377+
new (Ctx) SimpleIdentTypeRepr(DeclNameLoc(), DeclNameRef(firstIdent)));
378+
} while (!rest.empty());
379+
380+
TypeRepr *repr = components.size() == 1
381+
? static_cast<TypeRepr*>(components.front())
382+
: CompoundIdentTypeRepr::create(Ctx, components);
383+
Type ty = swift::performTypeResolution(repr, Ctx, /*isSILMode=*/false,
384+
/*isSILType=*/false,
385+
/*GenericEnv=*/nullptr,
386+
/*GenericParams=*/nullptr,
387+
&SF);
388+
cycleViaDecls.push_back(ty->getNominalOrBoundGenericNominal());
359389
}
390+
391+
(void)evaluateOrDefault(Ctx.evaluator, DebugIntentionallyCauseCycleRequest{
392+
cycleViaDecls.front(),
393+
makeArrayRef(cycleViaDecls).drop_front() },
394+
{});
395+
}
396+
397+
evaluator::SideEffect
398+
DebugIntentionallyCauseCycleRequest::evaluate(Evaluator &evaluator, Decl *first,
399+
ArrayRef<Decl *> rest) const {
400+
if (rest.empty())
401+
return evaluateOrDefault(evaluator, *this, {});
402+
403+
SmallVector<Decl *, 4> newRest;
404+
llvm::append_range(newRest, rest.drop_front());
405+
newRest.push_back(first);
406+
407+
return evaluateOrDefault(evaluator, DebugIntentionallyCauseCycleRequest{
408+
rest.front(), newRest },
409+
{});
360410
}
361411

362412
bool swift::isAdditiveArithmeticConformanceDerivationEnabled(SourceFile &SF) {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -parse-as-library -emit-module %S/Inputs/custom-modules/CircularLibrary.swift -I %S/Inputs/custom-modules -o %t/CircularLibrary.swiftmodule
3+
// RUN: not %target-swift-frontend -typecheck %s -I %S/Inputs/custom-modules -I %t -debug-cause-cycle-via-decl MyWholeNumber.RawValue -debug-cause-cycle-via-decl MyWholeNumber -debug-cause-cycle-via-decl MyNumberwang -debug-cycles 2>%t.txt
4+
// RUN: %FileCheck %s <%t.txt
5+
6+
// REQUIRES: objc_interop
7+
8+
import CircularLibrary
9+
10+
// FIXME: This first failure is actually a bug! It's a purer form of the test
11+
// case in test/CircularReferences/overlay_extensions.swift. (SR-14324)
12+
// CHECK-LABEL: {{^}}=== CYCLE DETECTED ===
13+
// CHECK-NOT: error: circular reference
14+
// CHECK: {{^}}==> DirectLookupRequest(directly looking up 'RawValue' on CircularLibrary.(file).MyWholeNumber with options
15+
// CHECK-NEXT: {{^}}CircularLibrary.MyWholeNumber:{{[0-9]+:[0-9]+}}: error: circular reference
16+
// CHECK-NEXT: {{^}}public enum MyWholeNumber : UInt32 {
17+
// CHECK-NEXT: {{^}} ^
18+
19+
// This failure is the one the test case is actually intended to cause.
20+
// CHECK-LABEL: {{^}}=== CYCLE DETECTED ===
21+
// CHECK-NEXT: {{^}}==> DebugIntentionallyCauseCycleRequest(Swift.(file).UInt32, {CircularLibrary.(file).MyWholeNumber, CircularLibrary.(file).MyNumberwang})
22+
// CHECK-NEXT: {{^}} DebugIntentionallyCauseCycleRequest(CircularLibrary.(file).MyWholeNumber, {CircularLibrary.(file).MyNumberwang, Swift.(file).UInt32})
23+
// CHECK-NEXT: {{^}} DebugIntentionallyCauseCycleRequest(CircularLibrary.(file).MyNumberwang, {Swift.(file).UInt32, CircularLibrary.(file).MyWholeNumber})
24+
// CHECK-NEXT: {{^}}Swift.UInt32:{{[0-9]+:[0-9]+}}: error: circular reference
25+
// CHECK-NEXT: {{^}}@frozen public struct UInt32 : FixedWidthInteger, UnsignedInteger, _ExpressibleByBuiltinIntegerLiteral {
26+
// CHECK-NEXT: {{^}} ^
27+
// CHECK-NEXT: {{^}}CircularLibrary.MyNumberwang:{{[0-9]+:[0-9]+}}: note: through reference here
28+
// CHECK-NEXT: {{^}}open class MyNumberwang {
29+
// CHECK-NEXT: {{^}} ^
30+
// CHECK-NEXT: {{^}}CircularLibrary.MyWholeNumber:{{[0-9]+:[0-9]+}}: note: through reference here
31+
// CHECK-NEXT: {{^}}public enum MyWholeNumber : UInt32 {
32+
// CHECK-NEXT: {{^}} ^

0 commit comments

Comments
 (0)