Skip to content

Commit ed8ae16

Browse files
[lldb] Implement swift diagnostic for non-captured vars in "expr"
This commit implements the same diagnostic that was previously implemented for frame var. The strategy here is slightly different due to how expression evaluation works. We have to pattern match this diagnostic from the compiler: ``` error: <EXPR>:6:1: cannot find 'NAME' in scope 1 + NAME ^~~~ ``` If we find it, we trigger the usual search for a variable named `NAME`. If it is found, the diagnostic is replaced with: ``` error: <EXPR>:6:5: Current frame is a closure. A variable named 'NAME' exists in function 'foo(_:)', but it was not captured. Hint: the variable may be available in a parent frame. 1 + NAME ^~~~ ``` Implementation note: the pattern matching could be a _lot_ simpler if we don't insist on replacing the original diagnostic and, instead, append to it.
1 parent 2d6db4e commit ed8ae16

File tree

2 files changed

+99
-0
lines changed

2 files changed

+99
-0
lines changed

lldb/source/Plugins/ExpressionParser/Swift/SwiftUserExpression.cpp

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "lldb/Symbol/Type.h"
3636
#include "lldb/Symbol/Variable.h"
3737
#include "lldb/Symbol/VariableList.h"
38+
#include "lldb/Target/Language.h"
3839
#include "lldb/Utility/LLDBAssert.h"
3940
#include "lldb/Utility/LLDBLog.h"
4041
#include "lldb/Utility/Log.h"
@@ -693,6 +694,95 @@ SwiftUserExpression::GetTextAndSetExpressionParser(
693694
return parse_result;
694695
}
695696

697+
/// If `sc` represents a "closure-like" function according to `lang`, and
698+
/// `var_name` can be found in a parent context, create a diagnostic
699+
/// explaining that this variable is available but not captured by the closure.
700+
static std::string
701+
CreateVarInParentScopeDiagnostic(StringRef var_name,
702+
StringRef parent_func_name) {
703+
return llvm::formatv("Current frame is a closure.\nA variable named '{0}' "
704+
"exists in function '{1}', but it "
705+
"was not captured.\nHint: the variable may be available "
706+
"in a parent frame.",
707+
var_name, parent_func_name);
708+
}
709+
710+
namespace {
711+
/// Helper class to match the following two diagnostics emitted by the compiler:
712+
// (.*): cannot find '([^']+)' in scope\n(.*)");
713+
// (.*): use of undeclared identifier '([^']+)'\n(.*)");
714+
// The first group is stored in "prefix", the middle group in "var_name", and
715+
// the final group in "suffix".
716+
struct VariableNotFoundDiagnosticMatcher {
717+
static std::optional<VariableNotFoundDiagnosticMatcher>
718+
match(StringRef diagnostic) {
719+
llvm::SmallVector<StringRef, 4> match_groups;
720+
if (not_in_scope_regex1.Execute(diagnostic, &match_groups) ||
721+
not_in_scope_regex2.Execute(diagnostic, &match_groups))
722+
return VariableNotFoundDiagnosticMatcher{match_groups[1], match_groups[2],
723+
match_groups[3]};
724+
return {};
725+
}
726+
727+
StringRef prefix;
728+
StringRef var_name;
729+
StringRef suffix;
730+
731+
private:
732+
static const RegularExpression not_in_scope_regex1;
733+
static const RegularExpression not_in_scope_regex2;
734+
};
735+
const RegularExpression VariableNotFoundDiagnosticMatcher::not_in_scope_regex1 =
736+
RegularExpression("(.*): cannot find '([^']+)' in scope\n(.*)");
737+
const RegularExpression VariableNotFoundDiagnosticMatcher::not_in_scope_regex2 =
738+
RegularExpression("(.*): use of undeclared identifier '([^']+)'\n(.*)");
739+
} // namespace
740+
741+
/// If `diagnostic_manager` contains a "cannot find <var_name> in scope"
742+
/// diagnostic, attempt to enhance it by showing if `var_name` is used inside a
743+
/// closure, not captured, but defined in a parent scope.
744+
static void EnhanceNotInScopeDiagnostics(DiagnosticManager &diagnostic_manager,
745+
ExecutionContextScope *exe_scope) {
746+
if (!exe_scope)
747+
return;
748+
lldb::StackFrameSP stack_frame = exe_scope->CalculateStackFrame();
749+
if (!stack_frame)
750+
return;
751+
SymbolContext sc =
752+
stack_frame->GetSymbolContext(lldb::eSymbolContextEverything);
753+
Language *swift_lang =
754+
Language::FindPlugin(lldb::LanguageType::eLanguageTypeSwift);
755+
if (!swift_lang)
756+
return;
757+
758+
for (auto &diag : diagnostic_manager.Diagnostics()) {
759+
if (!diag)
760+
continue;
761+
762+
StringRef old_rendered_msg = diag->GetDetail().rendered;
763+
std::optional<VariableNotFoundDiagnosticMatcher> matched_diag =
764+
VariableNotFoundDiagnosticMatcher::match(old_rendered_msg);
765+
if (!matched_diag)
766+
continue;
767+
768+
Function *parent_func =
769+
swift_lang->FindParentOfClosureWithVariable(matched_diag->var_name, sc);
770+
if (!parent_func)
771+
continue;
772+
std::string new_message = CreateVarInParentScopeDiagnostic(
773+
matched_diag->var_name, parent_func->GetDisplayName());
774+
775+
std::string new_rendered =
776+
llvm::formatv("{0}: {1}\n{2}", matched_diag->prefix, new_message,
777+
matched_diag->suffix);
778+
const DiagnosticDetail &old_detail = diag->GetDetail();
779+
diag = std::make_unique<Diagnostic>(
780+
diag->getKind(), diag->GetCompilerID(),
781+
DiagnosticDetail{old_detail.source_location, old_detail.severity,
782+
std::move(new_message), std::move(new_rendered)});
783+
}
784+
}
785+
696786
bool SwiftUserExpression::Parse(DiagnosticManager &diagnostic_manager,
697787
ExecutionContext &exe_ctx,
698788
lldb_private::ExecutionPolicy execution_policy,
@@ -888,6 +978,7 @@ bool SwiftUserExpression::Parse(DiagnosticManager &diagnostic_manager,
888978
fixed_expression.substr(fixed_start, fixed_end - fixed_start);
889979
}
890980
}
981+
EnhanceNotInScopeDiagnostics(diagnostic_manager, exe_scope);
891982
return false;
892983
case ParseResult::success:
893984
llvm_unreachable("Success case is checked separately before switch!");

lldb/test/API/lang/swift/closures_var_not_captured/TestSwiftClosureVarNotCaptured.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ def check_not_captured_error(test, frame, var_name, parent_function):
1414
expected_error = (
1515
f"A variable named '{var_name}' exists in function '{parent_function}'"
1616
)
17+
value = frame.EvaluateExpression(var_name)
18+
error = value.GetError().GetCString()
19+
test.assertIn(expected_error, error)
20+
21+
value = frame.EvaluateExpression(f"1 + {var_name} + 1")
22+
error = value.GetError().GetCString()
23+
test.assertIn(expected_error, error)
24+
1725
test.expect(f"frame variable {var_name}", substrs=[expected_error], error=True)
1826
test.expect(f"frame variable {var_name} + 1", substrs=[expected_error], error=True)
1927

0 commit comments

Comments
 (0)