|
35 | 35 | #include "lldb/Symbol/Type.h"
|
36 | 36 | #include "lldb/Symbol/Variable.h"
|
37 | 37 | #include "lldb/Symbol/VariableList.h"
|
| 38 | +#include "lldb/Target/Language.h" |
38 | 39 | #include "lldb/Utility/LLDBAssert.h"
|
39 | 40 | #include "lldb/Utility/LLDBLog.h"
|
40 | 41 | #include "lldb/Utility/Log.h"
|
@@ -693,6 +694,95 @@ SwiftUserExpression::GetTextAndSetExpressionParser(
|
693 | 694 | return parse_result;
|
694 | 695 | }
|
695 | 696 |
|
| 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 | + |
696 | 786 | bool SwiftUserExpression::Parse(DiagnosticManager &diagnostic_manager,
|
697 | 787 | ExecutionContext &exe_ctx,
|
698 | 788 | lldb_private::ExecutionPolicy execution_policy,
|
@@ -888,6 +978,7 @@ bool SwiftUserExpression::Parse(DiagnosticManager &diagnostic_manager,
|
888 | 978 | fixed_expression.substr(fixed_start, fixed_end - fixed_start);
|
889 | 979 | }
|
890 | 980 | }
|
| 981 | + EnhanceNotInScopeDiagnostics(diagnostic_manager, exe_scope); |
891 | 982 | return false;
|
892 | 983 | case ParseResult::success:
|
893 | 984 | llvm_unreachable("Success case is checked separately before switch!");
|
|
0 commit comments