Skip to content

Commit 08f7028

Browse files
authored
Merge pull request swiftlang#30482 from apple/syntax-incremental-parse-diagnostic
[Parse] Fix issue with incremental re-parsing for SwiftSyntax emitting bogus parsing error
2 parents fd82d87 + bea79d4 commit 08f7028

File tree

9 files changed

+107
-18
lines changed

9 files changed

+107
-18
lines changed

include/swift/Basic/LangOptions.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,11 @@ namespace swift {
296296
/// incrementals parsing.
297297
bool BuildSyntaxTree = false;
298298

299+
/// Whether parsing is occurring for creation of syntax tree only, and no typechecking will occur after
300+
/// parsing e.g. when parsing for SwiftSyntax. This is intended to affect parsing, e.g. disable
301+
/// unnecessary name lookups that are not useful for pure syntactic parsing.
302+
bool ParseForSyntaxTreeOnly = false;
303+
299304
/// Whether to verify the parsed syntax tree and emit related diagnostics.
300305
bool VerifySyntaxTree = false;
301306

lib/Parse/ParseExpr.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2264,7 +2264,10 @@ Expr *Parser::parseExprIdentifier() {
22642264
}
22652265

22662266
ValueDecl *D = nullptr;
2267-
if (!InPoundIfEnvironment) {
2267+
// When doing incremental re-parsing for SwiftSyntax this check may emit bogus
2268+
// diagnostic. Also really the syntactic parser should not be doing name
2269+
// lookups, so disable this check when parsing for SwiftSyntax.
2270+
if (!InPoundIfEnvironment && !Context.LangOpts.ParseForSyntaxTreeOnly) {
22682271
D = lookupInScope(name);
22692272
// FIXME: We want this to work: "var x = { x() }", but for now it's better
22702273
// to disallow it than to crash.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %validate-incrparse %s --test-case REPLACE
3+
4+
let myFont: Font = {
5+
let myFont = Font.body
6+
return myFont.weight(.<<REPLACE<bold|||light>>>)
7+
}()

tools/libSwiftSyntaxParser/libSwiftSyntaxParser.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ swiftparse_client_node_t SynParser::parse(const char *source) {
277277
TypeCheckerOptions tyckOpts;
278278
LangOptions langOpts;
279279
langOpts.BuildSyntaxTree = true;
280+
langOpts.ParseForSyntaxTreeOnly = true;
280281
langOpts.CollectParsedToken = false;
281282
// Disable name lookups during parsing.
282283
// Not ready yet:

tools/swift-syntax-test/swift-syntax-test.cpp

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@ static llvm::cl::opt<std::string>
175175
OutputFilename("output-filename",
176176
llvm::cl::desc("Path to the output file"));
177177

178+
static llvm::cl::opt<std::string>
179+
DiagsOutputFilename("diags-output-filename",
180+
llvm::cl::desc("Path to the output file for parser diagnostics text"));
181+
178182
static llvm::cl::opt<bool>
179183
PrintVisualReuseInfo("print-visual-reuse-info",
180184
llvm::cl::desc("Print a coloured output of which parts of "
@@ -556,12 +560,17 @@ bool verifyReusedRegions(ByteBasedSourceRangeSet ExpectedReparseRegions,
556560
return NoUnexpectedParse;
557561
}
558562

563+
struct ParseInfo {
564+
SourceFile *SF;
565+
SyntaxParsingCache *SyntaxCache;
566+
std::string Diags;
567+
};
568+
559569
/// Parse the given input file (incrementally if an old syntax tree was
560570
/// provided) and call the action specific callback with the new syntax tree
561571
int parseFile(
562572
const char *MainExecutablePath, const StringRef InputFileName,
563-
llvm::function_ref<int(SourceFile *, SyntaxParsingCache *SyntaxCache)>
564-
ActionSpecificCallback) {
573+
llvm::function_ref<int(ParseInfo)> ActionSpecificCallback) {
565574
// The cache needs to be a heap allocated pointer since we construct it inside
566575
// an if block but need to keep it alive until the end of the function.
567576
SyntaxParsingCache *SyntaxCache = nullptr;
@@ -597,6 +606,7 @@ int parseFile(
597606
// Set up the compiler invocation
598607
CompilerInvocation Invocation;
599608
Invocation.getLangOptions().BuildSyntaxTree = true;
609+
Invocation.getLangOptions().ParseForSyntaxTreeOnly = true;
600610
Invocation.getLangOptions().VerifySyntaxTree = options::VerifySyntaxTree;
601611
Invocation.getLangOptions().RequestEvaluatorGraphVizPath = options::GraphVisPath;
602612
Invocation.getFrontendOptions().InputsAndOutputs.addInputFile(InputFileName);
@@ -608,7 +618,9 @@ int parseFile(
608618
Invocation.setMainFileSyntaxParsingCache(SyntaxCache);
609619
Invocation.setModuleName("Test");
610620

611-
PrintingDiagnosticConsumer DiagConsumer;
621+
std::string DiagsString;
622+
llvm::raw_string_ostream DiagOS(DiagsString);
623+
PrintingDiagnosticConsumer DiagConsumer(DiagOS);
612624
CompilerInstance Instance;
613625
Instance.addDiagnosticConsumer(&DiagConsumer);
614626
if (Instance.setup(Invocation)) {
@@ -658,7 +670,8 @@ int parseFile(
658670
}
659671
}
660672

661-
int ActionSpecificExitCode = ActionSpecificCallback(SF, SyntaxCache);
673+
int ActionSpecificExitCode =
674+
ActionSpecificCallback({SF, SyntaxCache, DiagOS.str()});
662675
if (ActionSpecificExitCode != EXIT_SUCCESS) {
663676
return ActionSpecificExitCode;
664677
} else {
@@ -713,16 +726,18 @@ int doDumpRawTokenSyntax(const StringRef InputFile) {
713726
int doFullParseRoundTrip(const char *MainExecutablePath,
714727
const StringRef InputFile) {
715728
return parseFile(MainExecutablePath, InputFile,
716-
[](SourceFile *SF, SyntaxParsingCache *SyntaxCache) -> int {
717-
SF->getSyntaxRoot().print(llvm::outs(), {});
729+
[](ParseInfo info) -> int {
730+
info.SF->getSyntaxRoot().print(llvm::outs(), {});
718731
return EXIT_SUCCESS;
719732
});
720733
}
721734

722735
int doSerializeRawTree(const char *MainExecutablePath,
723736
const StringRef InputFile) {
724737
return parseFile(MainExecutablePath, InputFile,
725-
[](SourceFile *SF, SyntaxParsingCache *SyntaxCache) -> int {
738+
[](ParseInfo info) -> int {
739+
auto SF = info.SF;
740+
auto SyntaxCache = info.SyntaxCache;
726741
auto Root = SF->getSyntaxRoot().getRaw();
727742
std::unordered_set<unsigned> ReusedNodeIds;
728743
if (options::IncrementalSerialization && SyntaxCache) {
@@ -780,6 +795,23 @@ int doSerializeRawTree(const char *MainExecutablePath,
780795
SerializeTree(llvm::outs(), Root, SyntaxCache);
781796
}
782797
}
798+
799+
if (!options::DiagsOutputFilename.empty()) {
800+
std::error_code errorCode;
801+
llvm::raw_fd_ostream os(options::DiagsOutputFilename, errorCode,
802+
llvm::sys::fs::F_None);
803+
if (errorCode) {
804+
llvm::errs() << "error opening file '" << options::DiagsOutputFilename
805+
<< "': " << errorCode.message() << '\n';
806+
return EXIT_FAILURE;
807+
}
808+
if (!info.Diags.empty())
809+
os << info.Diags << '\n';
810+
} else {
811+
if (!info.Diags.empty())
812+
llvm::errs() << info.Diags << '\n';
813+
}
814+
783815
return EXIT_SUCCESS;
784816
});
785817
}
@@ -800,27 +832,28 @@ int doDeserializeRawTree(const char *MainExecutablePath,
800832

801833
int doParseOnly(const char *MainExecutablePath, const StringRef InputFile) {
802834
return parseFile(MainExecutablePath, InputFile,
803-
[](SourceFile *SF, SyntaxParsingCache *SyntaxCache) {
804-
return SF ? EXIT_SUCCESS : EXIT_FAILURE;
835+
[](ParseInfo info) {
836+
return info.SF ? EXIT_SUCCESS : EXIT_FAILURE;
805837
});
806838
}
807839

808840
int dumpParserGen(const char *MainExecutablePath, const StringRef InputFile) {
809841
return parseFile(MainExecutablePath, InputFile,
810-
[](SourceFile *SF, SyntaxParsingCache *SyntaxCache) {
842+
[](ParseInfo info) {
811843
SyntaxPrintOptions Opts;
812844
Opts.PrintSyntaxKind = options::PrintNodeKind;
813845
Opts.Visual = options::Visual;
814846
Opts.PrintTrivialNodeKind = options::PrintTrivialNodeKind;
815-
SF->getSyntaxRoot().print(llvm::outs(), Opts);
847+
info.SF->getSyntaxRoot().print(llvm::outs(), Opts);
816848
return EXIT_SUCCESS;
817849
});
818850
}
819851

820852
int dumpEOFSourceLoc(const char *MainExecutablePath,
821853
const StringRef InputFile) {
822854
return parseFile(MainExecutablePath, InputFile,
823-
[](SourceFile *SF, SyntaxParsingCache *SyntaxCache) -> int {
855+
[](ParseInfo info) -> int {
856+
auto SF = info.SF;
824857
auto BufferId = *SF->getBufferID();
825858
auto Root = SF->getSyntaxRoot();
826859
auto AbPos = Root.getEOFToken().getAbsolutePosition();
@@ -841,8 +874,8 @@ int dumpEOFSourceLoc(const char *MainExecutablePath,
841874
}
842875
}// end of anonymous namespace
843876

844-
int invokeCommand(const char *MainExecutablePath,
845-
const StringRef InputSourceFilename) {
877+
static int invokeCommand(const char *MainExecutablePath,
878+
const StringRef InputSourceFilename) {
846879
int ExitCode = EXIT_SUCCESS;
847880

848881
switch (options::Action) {

utils/incrparse/incr_transfer_round_trip.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ def main():
161161
serialization_format=serialization_format,
162162
omit_node_ids=False,
163163
output_file=pre_edit_tree_file,
164+
diags_output_file=None,
164165
temp_dir=temp_dir,
165166
swift_syntax_test=swift_syntax_test,
166167
print_visual_reuse_info=False)
@@ -172,6 +173,7 @@ def main():
172173
serialization_format=serialization_format,
173174
omit_node_ids=False,
174175
output_file=incremental_tree_file,
176+
diags_output_file=None,
175177
temp_dir=temp_dir,
176178
swift_syntax_test=swift_syntax_test,
177179
print_visual_reuse_info=False)

utils/incrparse/incr_transfer_tree.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def main():
7272
serialization_format='json',
7373
omit_node_ids=False,
7474
output_file=incremental_serialized_file,
75+
diags_output_file=None,
7576
temp_dir=temp_dir + '/temp',
7677
swift_syntax_test=swift_syntax_test,
7778
print_visual_reuse_info=False)

utils/incrparse/test_util.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,9 @@ def prepareForIncrParse(test_file, test_case, pre_edit_file, post_edit_file,
133133

134134
def serializeIncrParseMarkupFile(test_file, test_case, mode,
135135
serialization_mode, serialization_format,
136-
omit_node_ids, output_file, temp_dir,
137-
swift_syntax_test, print_visual_reuse_info):
136+
omit_node_ids, output_file, diags_output_file,
137+
temp_dir, swift_syntax_test,
138+
print_visual_reuse_info):
138139
test_file_name = os.path.basename(test_file)
139140
pre_edit_file = temp_dir + '/' + test_file_name + '.' + test_case + \
140141
'.pre.swift'
@@ -171,6 +172,9 @@ def serializeIncrParseMarkupFile(test_file, test_case, mode,
171172
'-output-filename', output_file
172173
]
173174

175+
if diags_output_file:
176+
command.extend(['-diags-output-filename', diags_output_file])
177+
174178
if omit_node_ids:
175179
command.extend(['-omit-node-ids'])
176180

@@ -320,6 +324,7 @@ def main():
320324
serialization_format=serialization_format,
321325
omit_node_ids=omit_node_ids,
322326
output_file=output_file,
327+
diags_output_file=None,
323328
temp_dir=temp_dir,
324329
swift_syntax_test=swift_syntax_test,
325330
print_visual_reuse_info=visual_reuse_info)

utils/incrparse/validate_parse.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ def main():
6767
post_edit_serialized_file = temp_dir + '/' + test_file_name + '.' \
6868
+ test_case + '.post.json'
6969

70+
incremental_diags_file = temp_dir + '/' + test_file_name + '.' \
71+
+ test_case + '.diagsViaIncr.txt'
72+
post_edit_diags_file = temp_dir + '/' + test_file_name + '.' \
73+
+ test_case + '.post.diags.txt'
74+
7075
# Generate the syntax tree once incrementally and once from scratch
7176
try:
7277
serializeIncrParseMarkupFile(test_file=test_file,
@@ -76,6 +81,7 @@ def main():
7681
serialization_format='json',
7782
omit_node_ids=True,
7883
output_file=incremental_serialized_file,
84+
diags_output_file=incremental_diags_file,
7985
temp_dir=temp_dir + '/temp',
8086
swift_syntax_test=swift_syntax_test,
8187
print_visual_reuse_info=visual_reuse_info)
@@ -91,6 +97,7 @@ def main():
9197
serialization_format='json',
9298
omit_node_ids=True,
9399
output_file=post_edit_serialized_file,
100+
diags_output_file=post_edit_diags_file,
94101
temp_dir=temp_dir + '/temp',
95102
swift_syntax_test=swift_syntax_test,
96103
print_visual_reuse_info=visual_reuse_info)
@@ -104,7 +111,7 @@ def main():
104111
lines = difflib.unified_diff(open(incremental_serialized_file).readlines(),
105112
open(post_edit_serialized_file).readlines(),
106113
fromfile=incremental_serialized_file,
107-
tofile=incremental_serialized_file)
114+
tofile=post_edit_serialized_file)
108115
diff = '\n'.join(line for line in lines)
109116
if diff:
110117
print('Test case "%s" of %s FAILed' % (test_case, test_file),
@@ -114,6 +121,31 @@ def main():
114121
print(diff, file=sys.stderr)
115122
sys.exit(1)
116123

124+
# Verify that if the incremental parse resulted in parser diagnostics, those
125+
# diagnostics were also emitted during the full parse.
126+
# We can't just diff the outputs because the full parse includes diagnostics
127+
# from the whole file, while the incremental parse includes only a subset.
128+
# Each diagnostic is searched in the full parse diagnostics array but the
129+
# search for each diagnostic continues from where the previous search
130+
# stopped.
131+
incremental_diags = open(incremental_diags_file).readlines()
132+
post_edit_diags = open(post_edit_diags_file).readlines()
133+
full_idx = 0
134+
for diag in incremental_diags:
135+
while full_idx < len(post_edit_diags):
136+
if post_edit_diags[full_idx] == diag:
137+
break
138+
full_idx += 1
139+
if full_idx == len(post_edit_diags):
140+
print('Test case "%s" of %s FAILed' % (test_case, test_file),
141+
file=sys.stderr)
142+
print('Parser diagnostic of incremental parsing was not emitted '
143+
'during from-scratch parsing of post-edit file:',
144+
file=sys.stderr)
145+
print(diag, file=sys.stderr)
146+
sys.exit(1)
147+
full_idx += 1 # continue searching from the next diagnostic line.
148+
117149

118150
if __name__ == '__main__':
119151
main()

0 commit comments

Comments
 (0)