-
Notifications
You must be signed in to change notification settings - Fork 10.4k
/
Copy pathDiagnosticEngine.cpp
1694 lines (1473 loc) · 60 KB
/
DiagnosticEngine.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//===--- DiagnosticEngine.cpp - Diagnostic Display Engine -----------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file defines the DiagnosticEngine class, which manages any diagnostics
// emitted by Swift.
//
//===----------------------------------------------------------------------===//
#include "swift/AST/DiagnosticEngine.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/ASTPrinter.h"
#include "swift/AST/Decl.h"
#include "swift/AST/DiagnosticGroups.h"
#include "swift/AST/DiagnosticList.h"
#include "swift/AST/DiagnosticSuppression.h"
#include "swift/AST/DiagnosticsCommon.h"
#include "swift/AST/DiagnosticsFrontend.h"
#include "swift/AST/Expr.h"
#include "swift/AST/Module.h"
#include "swift/AST/Pattern.h"
#include "swift/AST/PrintOptions.h"
#include "swift/AST/SourceFile.h"
#include "swift/AST/Stmt.h"
#include "swift/AST/TypeCheckRequests.h"
#include "swift/AST/TypeRepr.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/SourceManager.h"
#include "swift/Config.h"
#include "swift/Localization/LocalizationFormat.h"
#include "swift/Parse/Lexer.h" // bad dependency
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/AST/Type.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/raw_ostream.h"
using namespace swift;
static_assert(IsTriviallyDestructible<ZeroArgDiagnostic>::value,
"ZeroArgDiagnostic is meant to be trivially destructable");
namespace {
enum class DiagnosticOptions {
/// No options.
none,
/// The location of this diagnostic points to the beginning of the first
/// token that the parser considers invalid. If this token is located at the
/// beginning of the line, then the location is adjusted to point to the end
/// of the previous token.
///
/// This behavior improves experience for "expected token X" diagnostics.
PointsToFirstBadToken,
/// After a fatal error subsequent diagnostics are suppressed.
Fatal,
/// An API or ABI breakage diagnostic emitted by the API digester.
APIDigesterBreakage,
/// A deprecation warning or error.
Deprecation,
/// A diagnostic warning about an unused element.
NoUsage,
/// The diagnostic should be ignored by default, but will be re-enabled
/// by various warning options (-Wwarning, -Werror). This only makes sense
/// for warnings.
DefaultIgnore,
};
struct StoredDiagnosticInfo {
DiagnosticKind kind : 2;
bool pointsToFirstBadToken : 1;
bool isFatal : 1;
bool isAPIDigesterBreakage : 1;
bool isDeprecation : 1;
bool isNoUsage : 1;
bool defaultIgnore : 1;
DiagGroupID groupID;
constexpr StoredDiagnosticInfo(DiagnosticKind k, bool firstBadToken,
bool fatal, bool isAPIDigesterBreakage,
bool deprecation, bool noUsage,
bool defaultIgnore, DiagGroupID groupID)
: kind(k), pointsToFirstBadToken(firstBadToken), isFatal(fatal),
isAPIDigesterBreakage(isAPIDigesterBreakage),
isDeprecation(deprecation), isNoUsage(noUsage),
defaultIgnore(defaultIgnore), groupID(groupID) {}
constexpr StoredDiagnosticInfo(DiagnosticKind k, DiagnosticOptions opts,
DiagGroupID groupID)
: StoredDiagnosticInfo(k,
opts == DiagnosticOptions::PointsToFirstBadToken,
opts == DiagnosticOptions::Fatal,
opts == DiagnosticOptions::APIDigesterBreakage,
opts == DiagnosticOptions::Deprecation,
opts == DiagnosticOptions::NoUsage,
opts == DiagnosticOptions::DefaultIgnore,
groupID) {}
};
} // end anonymous namespace
// TODO: categorization
static const constexpr StoredDiagnosticInfo storedDiagnosticInfos[] = {
#define GROUPED_ERROR(ID, Group, Options, Text, Signature) \
StoredDiagnosticInfo(DiagnosticKind::Error, DiagnosticOptions::Options, \
DiagGroupID::Group),
#define GROUPED_WARNING(ID, Group, Options, Text, Signature) \
StoredDiagnosticInfo(DiagnosticKind::Warning, DiagnosticOptions::Options, \
DiagGroupID::Group),
#define NOTE(ID, Options, Text, Signature) \
StoredDiagnosticInfo(DiagnosticKind::Note, DiagnosticOptions::Options, \
DiagGroupID::no_group),
#define REMARK(ID, Options, Text, Signature) \
StoredDiagnosticInfo(DiagnosticKind::Remark, DiagnosticOptions::Options, \
DiagGroupID::no_group),
#include "swift/AST/DiagnosticsAll.def"
};
static_assert(sizeof(storedDiagnosticInfos) / sizeof(StoredDiagnosticInfo) ==
NumDiagIDs,
"array size mismatch");
static constexpr const char * const diagnosticStrings[] = {
#define DIAG(KIND, ID, Group, Options, Text, Signature) Text,
#include "swift/AST/DiagnosticsAll.def"
"<not a diagnostic>",
};
static constexpr const char *const diagnosticIDStrings[] = {
#define DIAG(KIND, ID, Group, Options, Text, Signature) #ID,
#include "swift/AST/DiagnosticsAll.def"
"<not a diagnostic>",
};
static constexpr const char *const fixItStrings[] = {
#define DIAG(KIND, ID, Group, Options, Text, Signature)
#define FIXIT(ID, Text, Signature) Text,
#include "swift/AST/DiagnosticsAll.def"
"<not a fix-it>",
};
#define EDUCATIONAL_NOTES(DIAG, ...) \
static constexpr const char *const DIAG##_educationalNotes[] = {__VA_ARGS__, \
nullptr};
#include "swift/AST/EducationalNotes.def"
// NOTE: sadly, while GCC and Clang support array designators in C++, they are
// not part of the standard at the moment, so Visual C++ doesn't support them.
// This construct allows us to provide a constexpr array initialized to empty
// values except in the cases that EducationalNotes.def are provided, similar to
// what the C array would have looked like.
template<int N>
struct EducationalNotes {
constexpr EducationalNotes() : value() {
for (auto i = 0; i < N; ++i) value[i] = {};
#define EDUCATIONAL_NOTES(DIAG, ...) \
value[static_cast<std::underlying_type_t<DiagID>>(DiagID::DIAG)] = \
DIAG##_educationalNotes;
#include "swift/AST/EducationalNotes.def"
}
const char *const *value[N];
};
static constexpr EducationalNotes<NumDiagIDs> _EducationalNotes =
EducationalNotes<NumDiagIDs>();
static constexpr auto educationalNotes = _EducationalNotes.value;
DiagnosticState::DiagnosticState() {
// Initialize our ignored diagnostics to defaults
ignoredDiagnostics.reserve(NumDiagIDs);
for (const auto &info : storedDiagnosticInfos) {
ignoredDiagnostics.push_back(info.defaultIgnore);
}
// Initialize warningsAsErrors to default
warningsAsErrors.resize(DiagGroupsCount);
}
Diagnostic::Diagnostic(DiagID ID)
: Diagnostic(ID, storedDiagnosticInfos[(unsigned)ID].groupID) {}
static CharSourceRange toCharSourceRange(SourceManager &SM, SourceRange SR) {
return CharSourceRange(SM, SR.Start, Lexer::getLocForEndOfToken(SM, SR.End));
}
static CharSourceRange toCharSourceRange(SourceManager &SM, SourceLoc Start,
SourceLoc End) {
return CharSourceRange(SM, Start, End);
}
/// Extract a character at \p Loc. If \p Loc is the end of the buffer,
/// return '\f'.
static char extractCharAfter(SourceManager &SM, SourceLoc Loc) {
auto chars = SM.extractText({Loc, 1});
return chars.empty() ? '\f' : chars[0];
}
/// Extract a character immediately before \p Loc. If \p Loc is the
/// start of the buffer, return '\f'.
static char extractCharBefore(SourceManager &SM, SourceLoc Loc) {
// We have to be careful not to go off the front of the buffer.
auto bufferID = SM.findBufferContainingLoc(Loc);
auto bufferRange = SM.getRangeForBuffer(bufferID);
if (bufferRange.getStart() == Loc)
return '\f';
auto chars = SM.extractText({Loc.getAdvancedLoc(-1), 1}, bufferID);
assert(!chars.empty() && "Couldn't extractText with valid range");
return chars[0];
}
InFlightDiagnostic &InFlightDiagnostic::highlight(SourceRange R) {
assert(IsActive && "Cannot modify an inactive diagnostic");
if (Engine && R.isValid())
Engine->getActiveDiagnostic()
.addRange(toCharSourceRange(Engine->SourceMgr, R));
return *this;
}
InFlightDiagnostic &InFlightDiagnostic::highlightChars(SourceLoc Start,
SourceLoc End) {
assert(IsActive && "Cannot modify an inactive diagnostic");
if (Engine && Start.isValid())
Engine->getActiveDiagnostic()
.addRange(toCharSourceRange(Engine->SourceMgr, Start, End));
return *this;
}
InFlightDiagnostic &InFlightDiagnostic::highlightChars(CharSourceRange Range) {
assert(IsActive && "Cannot modify an inactive diagnostic");
if (Engine && Range.getStart().isValid())
Engine->getActiveDiagnostic().addRange(Range);
return *this;
}
/// Add an insertion fix-it to the currently-active diagnostic. The
/// text is inserted immediately *after* the token specified.
///
InFlightDiagnostic &
InFlightDiagnostic::fixItInsertAfter(SourceLoc L, StringRef FormatString,
ArrayRef<DiagnosticArgument> Args) {
L = Lexer::getLocForEndOfToken(Engine->SourceMgr, L);
return fixItInsert(L, FormatString, Args);
}
/// Add a token-based removal fix-it to the currently-active
/// diagnostic.
InFlightDiagnostic &InFlightDiagnostic::fixItRemove(SourceRange R) {
assert(IsActive && "Cannot modify an inactive diagnostic");
if (R.isInvalid() || !Engine) return *this;
// Convert from a token range to a CharSourceRange, which points to the end of
// the token we want to remove.
auto &SM = Engine->SourceMgr;
auto charRange = toCharSourceRange(SM, R);
// If we're removing something (e.g. a keyword), do a bit of extra work to
// make sure that we leave the code in a good place, without extraneous white
// space around its hole. Specifically, check to see there is whitespace
// before and after the end of range. If so, nuke the space afterward to keep
// things consistent.
if (extractCharAfter(SM, charRange.getEnd()) == ' ' &&
isspace(extractCharBefore(SM, charRange.getStart()))) {
charRange = CharSourceRange(charRange.getStart(),
charRange.getByteLength()+1);
}
Engine->getActiveDiagnostic().addFixIt(Diagnostic::FixIt(charRange, {}, {}));
return *this;
}
InFlightDiagnostic &
InFlightDiagnostic::fixItReplace(SourceRange R, StringRef FormatString,
ArrayRef<DiagnosticArgument> Args) {
auto &SM = Engine->SourceMgr;
auto charRange = toCharSourceRange(SM, R);
Engine->getActiveDiagnostic().addFixIt(
Diagnostic::FixIt(charRange, FormatString, Args));
return *this;
}
InFlightDiagnostic &InFlightDiagnostic::fixItReplace(SourceRange R,
StringRef Str) {
if (Str.empty())
return fixItRemove(R);
assert(IsActive && "Cannot modify an inactive diagnostic");
if (R.isInvalid() || !Engine) return *this;
auto &SM = Engine->SourceMgr;
auto charRange = toCharSourceRange(SM, R);
// If we're replacing with something that wants spaces around it, do a bit of
// extra work so that we don't suggest extra spaces.
// FIXME: This could probably be applied to structured fix-its as well.
if (Str.back() == ' ') {
if (isspace(extractCharAfter(SM, charRange.getEnd())))
Str = Str.drop_back();
}
if (!Str.empty() && Str.front() == ' ') {
if (isspace(extractCharBefore(SM, charRange.getStart())))
Str = Str.drop_front();
}
return fixItReplace(R, "%0", {Str});
}
InFlightDiagnostic &
InFlightDiagnostic::fixItReplaceChars(SourceLoc Start, SourceLoc End,
StringRef FormatString,
ArrayRef<DiagnosticArgument> Args) {
assert(IsActive && "Cannot modify an inactive diagnostic");
if (Engine && Start.isValid())
Engine->getActiveDiagnostic().addFixIt(
Diagnostic::FixIt(toCharSourceRange(Engine->SourceMgr, Start, End),
FormatString, Args));
return *this;
}
SourceLoc
DiagnosticEngine::getBestAddImportFixItLoc(const Decl *Member,
SourceFile *sourceFile) const {
auto &SM = SourceMgr;
SourceLoc bestLoc;
auto SF =
sourceFile ? sourceFile : Member->getDeclContext()->getParentSourceFile();
if (!SF) {
return bestLoc;
}
for (auto item : SF->getTopLevelItems()) {
// If we found an import declaration, we want to insert after it.
if (auto importDecl =
dyn_cast_or_null<ImportDecl>(item.dyn_cast<Decl *>())) {
SourceLoc loc = importDecl->getEndLoc();
if (loc.isValid()) {
bestLoc = Lexer::getLocForEndOfLine(SM, loc);
}
// Keep looking for more import declarations.
continue;
}
// If we got a location based on import declarations, we're done.
if (bestLoc.isValid())
break;
// For any other item, we want to insert before it.
SourceLoc loc = item.getStartLoc();
if (loc.isValid()) {
bestLoc = Lexer::getLocForStartOfLine(SM, loc);
break;
}
}
return bestLoc;
}
InFlightDiagnostic &InFlightDiagnostic::fixItAddImport(StringRef ModuleName) {
assert(IsActive && "Cannot modify an inactive diagnostic");
auto Member = Engine->ActiveDiagnostic->getDecl();
SourceLoc bestLoc = Engine->getBestAddImportFixItLoc(Member);
if (bestLoc.isValid()) {
llvm::SmallString<64> importText;
// @_spi imports.
if (Member->isSPI()) {
auto spiGroups = Member->getSPIGroups();
if (!spiGroups.empty()) {
importText += "@_spi(";
importText += spiGroups[0].str();
importText += ") ";
}
}
importText += "import ";
importText += ModuleName;
importText += "\n";
return fixItInsert(bestLoc, importText);
}
return *this;
}
InFlightDiagnostic &InFlightDiagnostic::fixItExchange(SourceRange R1,
SourceRange R2) {
assert(IsActive && "Cannot modify an inactive diagnostic");
auto &SM = Engine->SourceMgr;
// Convert from a token range to a CharSourceRange
auto charRange1 = toCharSourceRange(SM, R1);
auto charRange2 = toCharSourceRange(SM, R2);
// Extract source text.
auto text1 = SM.extractText(charRange1);
auto text2 = SM.extractText(charRange2);
Engine->getActiveDiagnostic().addFixIt(
Diagnostic::FixIt(charRange1, "%0", {text2}));
Engine->getActiveDiagnostic().addFixIt(
Diagnostic::FixIt(charRange2, "%0", {text1}));
return *this;
}
InFlightDiagnostic &
InFlightDiagnostic::limitBehavior(DiagnosticBehavior limit) {
Engine->getActiveDiagnostic().setBehaviorLimit(limit);
return *this;
}
InFlightDiagnostic &
InFlightDiagnostic::limitBehaviorUntilSwiftVersion(
DiagnosticBehavior limit, unsigned majorVersion) {
if (!Engine->languageVersion.isVersionAtLeast(majorVersion)) {
// If the behavior limit is a warning or less, wrap the diagnostic
// in a message that this will become an error in a later Swift
// version. We do this before limiting the behavior, because
// wrapIn will result in the behavior of the wrapping diagnostic.
if (limit >= DiagnosticBehavior::Warning) {
if (majorVersion > 6) {
wrapIn(diag::error_in_a_future_swift_lang_mode);
} else {
wrapIn(diag::error_in_swift_lang_mode, majorVersion);
}
}
limitBehavior(limit);
}
// Record all of the diagnostics that are going to be emitted.
if (majorVersion == 6 && limit != DiagnosticBehavior::Ignore) {
if (auto stats = Engine->statsReporter) {
++stats->getFrontendCounters().NumSwift6Errors;
}
}
return *this;
}
InFlightDiagnostic &
InFlightDiagnostic::warnUntilSwiftVersion(unsigned majorVersion) {
return limitBehaviorUntilSwiftVersion(DiagnosticBehavior::Warning,
majorVersion);
}
InFlightDiagnostic &
InFlightDiagnostic::warnInSwiftInterface(const DeclContext *context) {
if (context->isInSwiftinterface()) {
return limitBehavior(DiagnosticBehavior::Warning);
}
return *this;
}
InFlightDiagnostic &
InFlightDiagnostic::wrapIn(const Diagnostic &wrapper) {
// Save current active diagnostic into WrappedDiagnostics, ignoring state
// so we don't get a None return or influence future diagnostics.
DiagnosticState tempState;
Engine->state.swap(tempState);
llvm::SaveAndRestore<DiagnosticBehavior>
limit(Engine->getActiveDiagnostic().BehaviorLimit,
DiagnosticBehavior::Unspecified);
Engine->WrappedDiagnostics.push_back(*Engine->diagnosticInfoForDiagnostic(
Engine->getActiveDiagnostic(), /* includeDiagnosticName= */ false));
Engine->state.swap(tempState);
auto &wrapped = Engine->WrappedDiagnostics.back();
// Copy and update its arg list.
Engine->WrappedDiagnosticArgs.emplace_back(wrapped.FormatArgs);
wrapped.FormatArgs = Engine->WrappedDiagnosticArgs.back();
// Overwrite the ID and argument with those from the wrapper.
Engine->getActiveDiagnostic().ID = wrapper.ID;
Engine->getActiveDiagnostic().Args = wrapper.Args;
// Intentionally keeping the original GroupID here
// Set the argument to the diagnostic being wrapped.
assert(wrapper.getArgs().front().getKind() == DiagnosticArgumentKind::Diagnostic);
Engine->getActiveDiagnostic().Args.front() = &wrapped;
return *this;
}
void InFlightDiagnostic::flush() {
if (!IsActive)
return;
IsActive = false;
if (Engine)
Engine->flushActiveDiagnostic();
}
void Diagnostic::addChildNote(Diagnostic &&D) {
assert(storedDiagnosticInfos[(unsigned)D.ID].kind == DiagnosticKind::Note &&
"Only notes can have a parent.");
assert(storedDiagnosticInfos[(unsigned)ID].kind != DiagnosticKind::Note &&
"Notes can't have children.");
ChildNotes.push_back(std::move(D));
}
bool DiagnosticEngine::isDiagnosticPointsToFirstBadToken(DiagID ID) const {
return storedDiagnosticInfos[(unsigned) ID].pointsToFirstBadToken;
}
bool DiagnosticEngine::isAPIDigesterBreakageDiagnostic(DiagID ID) const {
return storedDiagnosticInfos[(unsigned)ID].isAPIDigesterBreakage;
}
bool DiagnosticEngine::isDeprecationDiagnostic(DiagID ID) const {
return storedDiagnosticInfos[(unsigned)ID].isDeprecation;
}
bool DiagnosticEngine::isNoUsageDiagnostic(DiagID ID) const {
return storedDiagnosticInfos[(unsigned)ID].isNoUsage;
}
bool DiagnosticEngine::finishProcessing() {
bool hadError = false;
for (auto &Consumer : Consumers) {
hadError |= Consumer->finishProcessing();
}
return hadError;
}
void DiagnosticEngine::setWarningsAsErrorsRules(
const std::vector<WarningAsErrorRule> &rules) {
std::vector<std::string> unknownGroups;
for (const auto &rule : rules) {
bool isEnabled = [&] {
switch (rule.getAction()) {
case WarningAsErrorRule::Action::Enable:
return true;
case WarningAsErrorRule::Action::Disable:
return false;
}
}();
auto target = rule.getTarget();
if (auto group = std::get_if<WarningAsErrorRule::TargetGroup>(&target)) {
auto name = std::string_view(group->name);
// Validate the group name and set the new behavior for each diagnostic
// associated with the group and all its subgroups.
if (auto groupID = getDiagGroupIDByName(name);
groupID && *groupID != DiagGroupID::no_group) {
getDiagGroupInfoByID(*groupID).traverseDepthFirst([&](auto group) {
state.setWarningsAsErrorsForDiagGroupID(*groupID, isEnabled);
for (DiagID diagID : group.diagnostics) {
state.setIgnoredDiagnostic(diagID, false);
}
});
} else {
unknownGroups.push_back(std::string(name));
}
} else if (std::holds_alternative<WarningAsErrorRule::TargetAll>(target)) {
state.setAllWarningsAsErrors(isEnabled);
} else {
llvm_unreachable("unhandled WarningAsErrorRule::Target");
}
}
for (const auto &unknownGroup : unknownGroups) {
diagnose(SourceLoc(), diag::unknown_warning_group, unknownGroup);
}
}
/// Skip forward to one of the given delimiters.
///
/// \param Text The text to search through, which will be updated to point
/// just after the delimiter.
///
/// \param Delim The first character delimiter to search for.
///
/// \param FoundDelim On return, true if the delimiter was found, or false
/// if the end of the string was reached.
///
/// \returns The string leading up to the delimiter, or the empty string
/// if no delimiter is found.
static StringRef
skipToDelimiter(StringRef &Text, char Delim, bool *FoundDelim = nullptr) {
unsigned Depth = 0;
if (FoundDelim)
*FoundDelim = false;
unsigned I = 0;
for (unsigned N = Text.size(); I != N; ++I) {
if (Text[I] == '{') {
++Depth;
continue;
}
if (Depth > 0) {
if (Text[I] == '}')
--Depth;
continue;
}
if (Text[I] == Delim) {
if (FoundDelim)
*FoundDelim = true;
break;
}
}
assert(Depth == 0 && "Unbalanced {} set in diagnostic text");
StringRef Result = Text.substr(0, I);
Text = Text.substr(I + 1);
return Result;
}
/// Handle the integer 'select' modifier. This is used like this:
/// %select{foo|bar|baz}2. This means that the integer argument "%2" has a
/// value from 0-2. If the value is 0, the diagnostic prints 'foo'.
/// If the value is 1, it prints 'bar'. If it has the value 2, it prints 'baz'.
/// This is very useful for certain classes of variant diagnostics.
static void formatSelectionArgument(StringRef ModifierArguments,
ArrayRef<DiagnosticArgument> Args,
unsigned SelectedIndex,
DiagnosticFormatOptions FormatOpts,
llvm::raw_ostream &Out) {
bool foundPipe = false;
do {
assert((!ModifierArguments.empty() || foundPipe) &&
"Index beyond bounds in %select modifier");
StringRef Text = skipToDelimiter(ModifierArguments, '|', &foundPipe);
if (SelectedIndex == 0) {
DiagnosticEngine::formatDiagnosticText(Out, Text, Args, FormatOpts);
break;
}
--SelectedIndex;
} while (true);
}
static bool isInterestingTypealias(Type type) {
// Dig out the typealias declaration, if there is one.
TypeAliasDecl *aliasDecl = nullptr;
if (auto aliasTy = dyn_cast<TypeAliasType>(type.getPointer()))
aliasDecl = aliasTy->getDecl();
else
return false;
if (type->isVoid())
return false;
// The 'Swift.AnyObject' typealias is not 'interesting'.
if (aliasDecl->getName() ==
aliasDecl->getASTContext().getIdentifier("AnyObject") &&
(aliasDecl->getParentModule()->isStdlibModule() ||
aliasDecl->getParentModule()->isBuiltinModule())) {
return false;
}
// Compatibility aliases are only interesting insofar as their underlying
// types are interesting.
if (aliasDecl->isCompatibilityAlias()) {
auto underlyingTy = aliasDecl->getUnderlyingType();
return isInterestingTypealias(underlyingTy);
}
// Builtin types are never interesting typealiases.
if (type->is<BuiltinType>()) return false;
return true;
}
/// Walks the type recursively desugaring types to display, but skipping
/// `GenericTypeParamType` because we would lose association with its original
/// declaration and end up presenting the parameter in τ_0_0 format on
/// diagnostic.
static Type getAkaTypeForDisplay(Type type) {
return type.transformRec([&](TypeBase *visitTy) -> std::optional<Type> {
if (isa<SugarType>(visitTy) &&
!isa<GenericTypeParamType>(visitTy))
return getAkaTypeForDisplay(visitTy->getDesugaredType());
return std::nullopt;
});
}
/// Decide whether to show the desugared type or not. We filter out some
/// cases to avoid too much noise.
static bool shouldShowAKA(Type type, StringRef typeName) {
// Canonical types are already desugared.
if (type->isCanonical())
return false;
// Only show 'aka' if there's a typealias involved; other kinds of sugar
// are easy enough for people to read on their own.
if (!type.findIf(isInterestingTypealias))
return false;
// If they are textually the same, don't show them. This can happen when
// they are actually different types, because they exist in different scopes
// (e.g. everyone names their type parameters 'T').
if (typeName == getAkaTypeForDisplay(type).getString())
return false;
return true;
}
/// If a type is part of an argument list which includes another, distinct type
/// with the same string representation, it should be qualified during
/// formatting.
static bool typeSpellingIsAmbiguous(Type type,
ArrayRef<DiagnosticArgument> Args,
PrintOptions &PO) {
for (auto arg : Args) {
if (arg.getKind() == DiagnosticArgumentKind::Type) {
auto argType = arg.getAsType();
if (argType && argType.getPointer() != type.getPointer() &&
argType.getString(PO) == type.getString(PO)) {
// Currently, existential types are spelled the same way
// as protocols and compositions. We can remove this once
// existenials are printed with 'any'.
if (type->is<ExistentialType>() || argType->isExistentialType()) {
auto constraint = type;
if (auto existential = type->getAs<ExistentialType>())
constraint = existential->getConstraintType();
auto argConstraint = argType;
if (auto existential = argType->getAs<ExistentialType>())
argConstraint = existential->getConstraintType();
if (constraint.getPointer() != argConstraint.getPointer())
return true;
continue;
}
return true;
}
}
}
return false;
}
void swift::printClangDeclName(const clang::NamedDecl *ND,
llvm::raw_ostream &os) {
ND->getNameForDiagnostic(os, ND->getASTContext().getPrintingPolicy(), false);
}
void swift::printClangTypeName(const clang::Type *Ty, llvm::raw_ostream &os) {
clang::QualType::print(Ty, clang::Qualifiers(), os,
clang::PrintingPolicy{clang::LangOptions()}, "");
}
/// Format a single diagnostic argument and write it to the given
/// stream.
static void formatDiagnosticArgument(StringRef Modifier,
StringRef ModifierArguments,
ArrayRef<DiagnosticArgument> Args,
unsigned ArgIndex,
DiagnosticFormatOptions FormatOpts,
llvm::raw_ostream &Out) {
const DiagnosticArgument &Arg = Args[ArgIndex];
switch (Arg.getKind()) {
case DiagnosticArgumentKind::Integer:
if (Modifier == "select") {
assert(Arg.getAsInteger() >= 0 && "Negative selection index");
formatSelectionArgument(ModifierArguments, Args, Arg.getAsInteger(),
FormatOpts, Out);
} else if (Modifier == "s") {
if (Arg.getAsInteger() != 1)
Out << 's';
} else {
assert(Modifier.empty() && "Improper modifier for integer argument");
Out << Arg.getAsInteger();
}
break;
case DiagnosticArgumentKind::Unsigned:
if (Modifier == "select") {
formatSelectionArgument(ModifierArguments, Args, Arg.getAsUnsigned(),
FormatOpts, Out);
} else if (Modifier == "s") {
if (Arg.getAsUnsigned() != 1)
Out << 's';
} else {
assert(Modifier.empty() && "Improper modifier for unsigned argument");
Out << Arg.getAsUnsigned();
}
break;
case DiagnosticArgumentKind::String:
if (Modifier == "select") {
formatSelectionArgument(ModifierArguments, Args,
Arg.getAsString().empty() ? 0 : 1, FormatOpts,
Out);
} else {
assert(Modifier.empty() && "Improper modifier for string argument");
Out << Arg.getAsString();
}
break;
case DiagnosticArgumentKind::Identifier:
if (Modifier == "select") {
formatSelectionArgument(ModifierArguments, Args,
Arg.getAsIdentifier() ? 1 : 0, FormatOpts,
Out);
} else {
assert(Modifier.empty() && "Improper modifier for identifier argument");
Out << FormatOpts.OpeningQuotationMark;
Arg.getAsIdentifier().printPretty(Out);
Out << FormatOpts.ClosingQuotationMark;
}
break;
case DiagnosticArgumentKind::ObjCSelector:
assert(Modifier.empty() && "Improper modifier for selector argument");
Out << FormatOpts.OpeningQuotationMark << Arg.getAsObjCSelector()
<< FormatOpts.ClosingQuotationMark;
break;
case DiagnosticArgumentKind::Decl: {
auto D = Arg.getAsDecl();
if (Modifier == "select") {
formatSelectionArgument(ModifierArguments, Args, D ? 1 : 0, FormatOpts,
Out);
break;
}
// Parse info out of modifier
bool includeKind = false;
bool includeName = true;
bool baseNameOnly = false;
if (Modifier == "kind") {
includeKind = true;
} else if (Modifier == "base") {
baseNameOnly = true;
} else if (Modifier == "kindbase") {
includeKind = true;
baseNameOnly = true;
} else if (Modifier == "kindonly") {
includeName = false;
} else {
assert(Modifier.empty() && "Improper modifier for ValueDecl argument");
}
// If it's an accessor, describe that and then switch to discussing its
// storage.
if (auto accessor = dyn_cast<AccessorDecl>(D)) {
Out << Decl::getDescriptiveKindName(D->getDescriptiveKind()) << " for ";
D = accessor->getStorage();
}
// If it's an extension, describe that and then switch to discussing its
// nominal type.
if (auto ext = dyn_cast<ExtensionDecl>(D)) {
Out << Decl::getDescriptiveKindName(D->getDescriptiveKind()) << " of ";
D = ext->getSelfNominalTypeDecl();
}
// Figure out the name we want to print.
DeclName name;
if (includeName) {
if (auto MD = dyn_cast<ModuleDecl>(D))
name = MD->getPublicModuleName(/*onlyIfImported=*/true);
else if (auto VD = dyn_cast<ValueDecl>(D))
name = VD->getName();
else if (auto PGD = dyn_cast<PrecedenceGroupDecl>(D))
name = PGD->getName();
else if (auto OD = dyn_cast<OperatorDecl>(D))
name = OD->getName();
else if (auto MMD = dyn_cast<MissingMemberDecl>(D))
name = MMD->getName();
if (baseNameOnly && name)
name = name.getBaseName();
}
// If the declaration is anonymous or we asked for a descriptive kind, print
// it.
if (!name || includeKind) {
Out << Decl::getDescriptiveKindName(D->getDescriptiveKind());
if (name)
Out << " ";
}
// Print the name.
if (name) {
Out << FormatOpts.OpeningQuotationMark;
name.printPretty(Out);
Out << FormatOpts.ClosingQuotationMark;
}
break;
}
case DiagnosticArgumentKind::FullyQualifiedType:
case DiagnosticArgumentKind::Type:
case DiagnosticArgumentKind::WitnessType: {
std::optional<DiagnosticFormatOptions> TypeFormatOpts;
if (Modifier == "noformat") {
TypeFormatOpts.emplace(DiagnosticFormatOptions::formatForFixIts());
} else {
assert(Modifier.empty() && "Improper modifier for Type argument");
TypeFormatOpts.emplace(FormatOpts);
}
// Strip extraneous parentheses; they add no value.
Type type;
bool needsQualification = false;
// Compute the appropriate print options for this argument.
auto printOptions = PrintOptions::forDiagnosticArguments();
if (Arg.getKind() == DiagnosticArgumentKind::Type) {
type = Arg.getAsType();
if (type->getASTContext().TypeCheckerOpts.PrintFullConvention)
printOptions.PrintFunctionRepresentationAttrs =
PrintOptions::FunctionRepresentationMode::Full;
needsQualification = typeSpellingIsAmbiguous(type, Args, printOptions);
} else if (Arg.getKind() == DiagnosticArgumentKind::FullyQualifiedType) {
type = Arg.getAsFullyQualifiedType().getType();
if (type->getASTContext().TypeCheckerOpts.PrintFullConvention)
printOptions.PrintFunctionRepresentationAttrs =
PrintOptions::FunctionRepresentationMode::Full;
needsQualification = true;
} else {
assert(Arg.getKind() == DiagnosticArgumentKind::WitnessType);
type = Arg.getAsWitnessType().getType();
printOptions.PrintGenericRequirements = false;
printOptions.PrintInverseRequirements = false;
needsQualification = typeSpellingIsAmbiguous(type, Args, printOptions);
}
// If a type has an unresolved type, print it with syntax sugar removed for
// clarity. For example, print `Array<_>` instead of `[_]`.
if (type->hasUnresolvedType()) {
type = type->getWithoutSyntaxSugar();
}
if (needsQualification &&
isa<OpaqueTypeArchetypeType>(type.getPointer()) &&
cast<ArchetypeType>(type.getPointer())->isRoot()) {
auto opaqueTypeDecl = type->castTo<OpaqueTypeArchetypeType>()->getDecl();
llvm::SmallString<256> NamingDeclText;
llvm::raw_svector_ostream OutNaming(NamingDeclText);
auto namingDecl = opaqueTypeDecl->getNamingDecl();
if (namingDecl->getDeclContext()->isTypeContext()) {
auto selfTy = namingDecl->getDeclContext()->getSelfInterfaceType();
selfTy->print(OutNaming);
OutNaming << '.';
}
namingDecl->getName().printPretty(OutNaming);
auto descriptiveKind = opaqueTypeDecl->getDescriptiveKind();
Out << llvm::format(TypeFormatOpts->OpaqueResultFormatString.c_str(),
type->getString(printOptions).c_str(),
Decl::getDescriptiveKindName(descriptiveKind).data(),
NamingDeclText.c_str());
} else {
printOptions.FullyQualifiedTypes = needsQualification;
std::string typeName = type->getString(printOptions);
if (shouldShowAKA(type, typeName)) {
llvm::SmallString<256> AkaText;
llvm::raw_svector_ostream OutAka(AkaText);
getAkaTypeForDisplay(type)->print(OutAka, printOptions);
Out << llvm::format(TypeFormatOpts->AKAFormatString.c_str(),
typeName.c_str(), AkaText.c_str());
} else {
Out << TypeFormatOpts->OpeningQuotationMark << typeName
<< TypeFormatOpts->ClosingQuotationMark;
}
}
break;
}
case DiagnosticArgumentKind::TypeRepr:
assert(Modifier.empty() && "Improper modifier for TypeRepr argument");
assert(Arg.getAsTypeRepr() && "TypeRepr argument is null");
Out << FormatOpts.OpeningQuotationMark << Arg.getAsTypeRepr()
<< FormatOpts.ClosingQuotationMark;
break;
case DiagnosticArgumentKind::DescriptivePatternKind:
assert(Modifier.empty() &&
"Improper modifier for DescriptivePatternKind argument");
Out << Pattern::getDescriptivePatternKindName(
Arg.getAsDescriptivePatternKind());