Skip to content

Commit ec07e03

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 3070750 commit ec07e03

File tree

2 files changed

+73
-0
lines changed

2 files changed

+73
-0
lines changed

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

Lines changed: 65 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,69 @@ 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+
/// If `diagnostic_manager` contains a "cannot find <var_name> in scope"
711+
/// diagnostic, attempt to enhance it by showing if `var_name` is used inside a
712+
/// closure, not captured, but defined in a parent scope.
713+
static void EnhanceNotInScopeDiagnostics(DiagnosticManager &diagnostic_manager,
714+
ExecutionContextScope *exe_scope) {
715+
if (!exe_scope)
716+
return;
717+
lldb::StackFrameSP stack_frame = exe_scope->CalculateStackFrame();
718+
if (!stack_frame)
719+
return;
720+
SymbolContext sc =
721+
stack_frame->GetSymbolContext(lldb::eSymbolContextEverything);
722+
Language *swift_lang =
723+
Language::FindPlugin(lldb::LanguageType::eLanguageTypeSwift);
724+
if (!swift_lang)
725+
return;
726+
727+
static const RegularExpression not_in_scope_regex =
728+
RegularExpression("(.*): cannot find '([^']+)' in scope\n(.*)");
729+
for (auto &diag : diagnostic_manager.Diagnostics()) {
730+
if (!diag)
731+
continue;
732+
733+
llvm::SmallVector<StringRef, 4> match_groups;
734+
735+
if (StringRef old_rendered_msg = diag->GetDetail().rendered;
736+
!not_in_scope_regex.Execute(old_rendered_msg, &match_groups))
737+
continue;
738+
739+
StringRef prefix = match_groups[1];
740+
StringRef var_name = match_groups[2];
741+
StringRef suffix = match_groups[3];
742+
743+
Function *parent_func =
744+
swift_lang->FindParentOfClosureWithVariable(var_name, sc);
745+
if (!parent_func)
746+
continue;
747+
std::string new_message = CreateVarInParentScopeDiagnostic(
748+
var_name, parent_func->GetDisplayName());
749+
750+
std::string new_rendered =
751+
llvm::formatv("{0}: {1}\n{2}", prefix, new_message, suffix);
752+
const DiagnosticDetail &old_detail = diag->GetDetail();
753+
diag = std::make_unique<Diagnostic>(
754+
diag->getKind(), diag->GetCompilerID(),
755+
DiagnosticDetail{old_detail.source_location, old_detail.severity,
756+
std::move(new_message), std::move(new_rendered)});
757+
}
758+
}
759+
696760
bool SwiftUserExpression::Parse(DiagnosticManager &diagnostic_manager,
697761
ExecutionContext &exe_ctx,
698762
lldb_private::ExecutionPolicy execution_policy,
@@ -888,6 +952,7 @@ bool SwiftUserExpression::Parse(DiagnosticManager &diagnostic_manager,
888952
fixed_expression.substr(fixed_start, fixed_end - fixed_start);
889953
}
890954
}
955+
EnhanceNotInScopeDiagnostics(diagnostic_manager, exe_scope);
891956
return false;
892957
case ParseResult::success:
893958
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

1927

0 commit comments

Comments
 (0)