diff --git a/lsp-server/src/language/semantic/analyzer.cppm b/lsp-server/src/language/semantic/analyzer.cppm index de2f989..a291275 100644 --- a/lsp-server/src/language/semantic/analyzer.cppm +++ b/lsp-server/src/language/semantic/analyzer.cppm @@ -11,6 +11,77 @@ import lsp.utils.string; namespace lsp::language::semantic { + namespace + { + std::optional UnquoteStringLiteral(std::string value) + { + if (value.size() < 2) + { + return std::nullopt; + } + + char first = value.front(); + char last = value.back(); + if ((first == '"' && last == '"') || (first == '\'' && last == '\'')) + { + value.erase(value.begin()); + value.pop_back(); + return value; + } + + return std::nullopt; + } + + std::optional> ParseQualifiedTypeName(const std::string& text) + { + std::string trimmed = utils::Trim(text); + if (trimmed.empty()) + { + return std::nullopt; + } + + std::string lower = utils::ToLower(trimmed); + if (lower.starts_with("unit(")) + { + std::string after_unit = trimmed.substr(5); + std::size_t close_paren = after_unit.find(')'); + if (close_paren == std::string::npos) + { + return std::nullopt; + } + + std::string unit_name = utils::Trim(after_unit.substr(0, close_paren)); + std::size_t dot_pos = after_unit.find('.', close_paren); + if (dot_pos == std::string::npos || dot_pos + 1 >= after_unit.size()) + { + return std::nullopt; + } + + std::string class_name = utils::Trim(after_unit.substr(dot_pos + 1)); + if (unit_name.empty() || class_name.empty()) + { + return std::nullopt; + } + + return std::make_pair(std::move(unit_name), std::move(class_name)); + } + + std::size_t dot_pos = trimmed.find_last_of('.'); + if (dot_pos == std::string::npos || dot_pos == 0 || dot_pos + 1 >= trimmed.size()) + { + return std::nullopt; + } + + std::string unit_name = utils::Trim(trimmed.substr(0, dot_pos)); + std::string class_name = utils::Trim(trimmed.substr(dot_pos + 1)); + if (unit_name.empty() || class_name.empty()) + { + return std::nullopt; + } + + return std::make_pair(std::move(unit_name), std::move(class_name)); + } + } Analyzer::Analyzer(symbol::SymbolTable& symbol_table, SemanticModel& semantic_model) : symbol_table_(symbol_table), @@ -737,7 +808,74 @@ namespace lsp::language::semantic } else if ([[maybe_unused]] auto* attr = dynamic_cast(node.target.get())) { - // 处理限定名的类型(如 SomeUnit.SomeClass) + const auto* class_ident = dynamic_cast(attr->attribute.get()); + if (class_ident && !class_ident->name.empty()) + { + std::optional unit_name; + if (const auto* unit_ident = dynamic_cast(attr->object.get())) + { + unit_name = unit_ident->name; + } + else if (const auto* unit_call = dynamic_cast(attr->object.get())) + { + if (const auto* callee_ident = dynamic_cast(unit_call->callee.get())) + { + if (utils::IEquals(callee_ident->name, "unit") && !unit_call->arguments.empty() && unit_call->arguments[0].value) + { + if (const auto* arg_ident = dynamic_cast(unit_call->arguments[0].value.get())) + { + unit_name = arg_ident->name; + } + } + } + } + + if (unit_name && !unit_name->empty()) + { + auto unit_id = ResolveIdentifier(*unit_name, class_ident->location); + if (unit_id) + { + const auto* unit_symbol = symbol_table_.definition(*unit_id); + if (unit_symbol && unit_symbol->Is()) + { + auto scope_id = FindScopeOwnedBy(*unit_id); + if (scope_id) + { + auto member_id = symbol_table_.scopes().FindSymbolInScope(*scope_id, class_ident->name); + if (member_id) + { + const auto* member_symbol = symbol_table_.definition(*member_id); + if (member_symbol && member_symbol->kind() == protocol::SymbolKind::Class) + { + TrackReference(*member_id, class_ident->location, false); + + auto class_scope_id = FindScopeOwnedBy(*member_id); + if (class_scope_id) + { + auto ctor_id = symbol_table_.scopes().FindSymbolInScope(*class_scope_id, class_ident->name); + if (ctor_id) + { + TrackCall(*ctor_id, node.span); + } + else + { + TrackCall(*member_id, node.span); + } + } + else + { + TrackCall(*member_id, node.span); + } + return; + } + } + } + } + } + } + } + + // fallback VisitExpression(*node.target); } else @@ -1150,6 +1288,56 @@ namespace lsp::language::semantic return type_system.CreateClassType(*class_id); } } + else if (auto* attr = dynamic_cast(new_expr->target.get())) + { + const auto* class_ident = dynamic_cast(attr->attribute.get()); + if (class_ident && !class_ident->name.empty()) + { + std::optional unit_name; + if (const auto* unit_ident = dynamic_cast(attr->object.get())) + { + unit_name = unit_ident->name; + } + else if (const auto* unit_call = dynamic_cast(attr->object.get())) + { + if (const auto* callee_ident = dynamic_cast(unit_call->callee.get())) + { + if (utils::IEquals(callee_ident->name, "unit") && !unit_call->arguments.empty() && unit_call->arguments[0].value) + { + if (const auto* arg_ident = dynamic_cast(unit_call->arguments[0].value.get())) + { + unit_name = arg_ident->name; + } + } + } + } + + if (unit_name && !unit_name->empty()) + { + auto unit_id = ResolveIdentifier(*unit_name, class_ident->location); + if (unit_id) + { + const auto* unit_symbol = symbol_table_.definition(*unit_id); + if (unit_symbol && unit_symbol->Is()) + { + auto scope_id = FindScopeOwnedBy(*unit_id); + if (scope_id) + { + auto member_id = symbol_table_.scopes().FindSymbolInScope(*scope_id, class_ident->name); + if (member_id) + { + const auto* member_symbol = symbol_table_.definition(*member_id); + if (member_symbol && member_symbol->kind() == protocol::SymbolKind::Class) + { + return type_system.CreateClassType(*member_id); + } + } + } + } + } + } + } + } } return type_system.GetUnknownType(); } @@ -1166,10 +1354,43 @@ namespace lsp::language::semantic { if (lit->literal_kind == ast::LiteralKind::kString) { - auto class_id = ResolveClassSymbol(lit->value, lit->location); - if (class_id) + std::string type_name = lit->value; + if (auto unquoted = UnquoteStringLiteral(type_name)) { - return type_system.CreateClassType(*class_id); + type_name = *unquoted; + } + + if (auto qualified = ParseQualifiedTypeName(type_name)) + { + auto unit_id = ResolveIdentifier(qualified->first, lit->location); + if (unit_id) + { + const auto* unit_symbol = symbol_table_.definition(*unit_id); + if (unit_symbol && unit_symbol->Is()) + { + auto scope_id = FindScopeOwnedBy(*unit_id); + if (scope_id) + { + auto member_id = symbol_table_.scopes().FindSymbolInScope(*scope_id, qualified->second); + if (member_id) + { + const auto* member_symbol = symbol_table_.definition(*member_id); + if (member_symbol && member_symbol->kind() == protocol::SymbolKind::Class) + { + return type_system.CreateClassType(*member_id); + } + } + } + } + } + } + else + { + auto class_id = ResolveClassSymbol(type_name, lit->location); + if (class_id) + { + return type_system.CreateClassType(*class_id); + } } } } diff --git a/lsp-server/src/language/symbol/internal/builder.cppm b/lsp-server/src/language/symbol/internal/builder.cppm index e94c906..58f5abc 100644 --- a/lsp-server/src/language/symbol/internal/builder.cppm +++ b/lsp-server/src/language/symbol/internal/builder.cppm @@ -50,8 +50,45 @@ namespace lsp::language::symbol if (const auto* attr = dynamic_cast(expr)) { - // Handle `unit(x).ClassName` by taking last attribute segment. - return ExtractClassNameFromExpression(attr->attribute.get()); + // Prefer preserving unit-qualified names like `UnitA.ClassName` / `unit(UnitA).ClassName`, + // fallback to last attribute segment if we can't extract a qualifier. + auto attr_name = ExtractClassNameFromExpression(attr->attribute.get()); + if (!attr_name || attr_name->empty()) + { + return std::nullopt; + } + + std::optional qualifier; + if (const auto* obj_ident = dynamic_cast(attr->object.get())) + { + if (!obj_ident->name.empty()) + { + qualifier = obj_ident->name; + } + } + else if (const auto* obj_call = dynamic_cast(attr->object.get())) + { + if (const auto* callee_ident = dynamic_cast(obj_call->callee.get())) + { + if (utils::IEquals(callee_ident->name, "unit") && !obj_call->arguments.empty() && obj_call->arguments[0].value) + { + if (const auto* arg_ident = dynamic_cast(obj_call->arguments[0].value.get())) + { + if (!arg_ident->name.empty()) + { + qualifier = "unit(" + arg_ident->name + ")"; + } + } + } + } + } + + if (qualifier && !qualifier->empty()) + { + return *qualifier + "." + *attr_name; + } + + return attr_name; } if (const auto* call = dynamic_cast(expr)) diff --git a/lsp-server/src/provider/text_document/completion.cppm b/lsp-server/src/provider/text_document/completion.cppm index 99737f4..18ccb34 100644 --- a/lsp-server/src/provider/text_document/completion.cppm +++ b/lsp-server/src/provider/text_document/completion.cppm @@ -610,6 +610,76 @@ namespace lsp::provider::text_document std::string member_prefix; }; + struct QualifiedPrefix + { + std::string unit_name; + std::string member_prefix; + }; + + std::optional ParseUnitQualifiedPrefix(const std::string& text) + { + std::string trimmed = utils::Trim(text); + if (trimmed.empty()) + { + return std::nullopt; + } + + std::string lower = utils::ToLower(trimmed); + + // unit(UnitA).Prefix + if (lower.starts_with("unit(")) + { + std::string after_unit = trimmed.substr(5); + std::size_t close_paren = after_unit.find(')'); + if (close_paren == std::string::npos) + { + return std::nullopt; + } + + std::string unit_name = utils::Trim(after_unit.substr(0, close_paren)); + std::size_t dot_pos = after_unit.find('.', close_paren); + std::string member_prefix; + if (dot_pos != std::string::npos && dot_pos + 1 <= after_unit.size()) + { + member_prefix = utils::Trim(after_unit.substr(dot_pos + 1)); + } + + if (unit_name.empty()) + { + return std::nullopt; + } + + return QualifiedPrefix{ + .unit_name = std::move(unit_name), + .member_prefix = std::move(member_prefix), + }; + } + + // UnitA.Prefix + std::size_t dot_pos = trimmed.find_last_of('.'); + if (dot_pos != std::string::npos) + { + std::string unit_name = utils::Trim(trimmed.substr(0, dot_pos)); + std::string member_prefix; + if (dot_pos + 1 <= trimmed.size()) + { + member_prefix = utils::Trim(trimmed.substr(dot_pos + 1)); + } + + if (unit_name.empty()) + { + return std::nullopt; + } + + return QualifiedPrefix{ + .unit_name = std::move(unit_name), + .member_prefix = std::move(member_prefix), + }; + } + + return std::nullopt; + } + std::optional DetectDotAccessContext(const std::string& line) { // unit(xxx).prefix @@ -667,6 +737,88 @@ namespace lsp::provider::text_document return ctx; } + std::vector CollectUnitSearchOrder(const language::symbol::SymbolTable& table, + const protocol::Position& position, + const std::string& content) + { + std::vector imports; + + std::uint32_t offset = utils::text_coordinates::ToOffset(position, content); + language::ast::Location loc{}; + loc.start_line = loc.end_line = position.line; + loc.start_column = loc.end_column = position.character; + loc.start_offset = loc.end_offset = offset; + + auto add_imports = [&imports](const auto& vec) { + imports.reserve(imports.size() + vec.size()); + for (const auto& imp : vec) + { + imports.push_back(imp.unit_name); + } + }; + + if (auto id_opt = table.FindSymbolAt(loc)) + { + if (const auto* sym = table.definition(*id_opt)) + { + if (const auto* func = sym->As()) + add_imports(func->imports); + else if (const auto* method = sym->As()) + add_imports(method->imports); + else if (const auto* cls = sym->As()) + add_imports(cls->imports); + else if (const auto* unit = sym->As()) + { + add_imports(unit->interface_imports); + add_imports(unit->implementation_imports); + } + } + } + + if (imports.empty()) + { + for (const auto& wrapper : table.all_definitions()) + { + const auto& sym = wrapper.get(); + if (sym.kind() == protocol::SymbolKind::Module) + { + if (const auto* unit = sym.As()) + { + add_imports(unit->interface_imports); + add_imports(unit->implementation_imports); + } + break; + } + } + } + + std::vector order; + std::unordered_set seen; + + std::string module_name = GetModuleName(table); + if (!module_name.empty()) + { + order.push_back(module_name); + seen.insert(module_name); + } + + for (auto it = imports.rbegin(); it != imports.rend(); ++it) + { + if (it->empty()) + { + continue; + } + if (seen.find(*it) != seen.end()) + { + continue; + } + order.push_back(*it); + seen.insert(*it); + } + + return order; + } + void AppendClassMethods(const language::symbol::SymbolTable& table, const language::symbol::Class& cls, const std::string& prefix, CompletionSource source, std::vector& out) { auto scope_id = FindScopeOwnedBy(table, language::symbol::ScopeKind::kClass, cls.id); @@ -1044,7 +1196,7 @@ namespace lsp::provider::text_document close == std::string::npos ? after.size() : close, dot == std::string::npos ? after.size() : dot); context.unit_name = utils::Trim(after.substr(0, name_end)); - if (!context.unit_name.empty() && IsUnitVisible(visible_units_set, context.unit_name)) + if (!context.unit_name.empty()) { context.is_unit_scoped_new = true; if (dot != std::string::npos && dot + 1 < after.size()) @@ -1055,11 +1207,11 @@ namespace lsp::provider::text_document } else { - auto dot = context.prefix.find('.'); + auto dot = context.prefix.find_last_of('.'); if (dot != std::string::npos) { auto alias = utils::Trim(context.prefix.substr(0, dot)); - if (!alias.empty() && IsUnitVisible(visible_units_set, alias)) + if (!alias.empty()) { context.is_unit_scoped_new = true; context.unit_name = alias; @@ -1136,25 +1288,57 @@ namespace lsp::provider::text_document { if (auto type_name = ResolveTypeNameInScope(*editing_table, editing_semantic, params.position, *content, context.object_name)) { - bool found_class = false; + std::string type_text = utils::Trim(*type_name); + std::string class_name = type_text; + std::vector unit_candidates; - auto try_append = [&](const language::symbol::SymbolTable& table, CompletionSource source) { - auto cls_opt = FindClassSymbol(table, *type_name); - if (!cls_opt) + if (auto qualified = ParseUnitQualifiedPrefix(type_text)) + { + if (!qualified->unit_name.empty() && !qualified->member_prefix.empty()) { - return; + unit_candidates.push_back(qualified->unit_name); + class_name = qualified->member_prefix; } - AppendInstanceMembers(table, (*cls_opt)->id(), context.member_prefix, source, collected); - found_class = true; - }; + } - try_append(*editing_table, CompletionSource::kEditing); - for (const auto* table : workspace_tables) - try_append(*table, CompletionSource::kWorkspace); - for (const auto* table : system_tables) - try_append(*table, CompletionSource::kSystem); + if (!class_name.empty()) + { + if (unit_candidates.empty()) + { + unit_candidates = CollectUnitSearchOrder(*editing_table, params.position, *content); + } - (void)found_class; + bool found_class = false; + for (const auto& unit_name : unit_candidates) + { + if (found_class) + break; + + auto try_table = [&](const language::symbol::SymbolTable& table, CompletionSource source) { + if (!utils::IEquals(GetModuleName(table), unit_name)) + { + return; + } + + auto cls_opt = FindClassSymbol(table, class_name); + if (!cls_opt) + { + return; + } + + AppendInstanceMembers(table, (*cls_opt)->id(), context.member_prefix, source, collected); + found_class = true; + }; + + try_table(*editing_table, CompletionSource::kEditing); + for (const auto* table : workspace_tables) + try_table(*table, CompletionSource::kWorkspace); + for (const auto* table : system_tables) + try_table(*table, CompletionSource::kSystem); + } + + (void)found_class; + } } } } @@ -1171,22 +1355,83 @@ namespace lsp::provider::text_document } else { - if (editing_table) - AppendClasses(*editing_table, context.prefix, CompletionSource::kEditing, collected, true); - for (const auto* table : workspace_tables) - AppendClasses(*table, context.prefix, CompletionSource::kWorkspace, collected, true); - for (const auto* table : system_tables) - AppendClasses(*table, context.prefix, CompletionSource::kSystem, collected, true); + if (editing_table && content) + { + auto unit_order = CollectUnitSearchOrder(*editing_table, params.position, *content); + if (!unit_order.empty()) + { + for (const auto& unit_name : unit_order) + { + AppendClasses(*editing_table, context.prefix, CompletionSource::kEditing, collected, true, unit_name); + for (const auto* table : workspace_tables) + AppendClasses(*table, context.prefix, CompletionSource::kWorkspace, collected, true, unit_name); + for (const auto* table : system_tables) + AppendClasses(*table, context.prefix, CompletionSource::kSystem, collected, true, unit_name); + } + } + else + { + AppendClasses(*editing_table, context.prefix, CompletionSource::kEditing, collected, true); + for (const auto* table : workspace_tables) + AppendClasses(*table, context.prefix, CompletionSource::kWorkspace, collected, true); + for (const auto* table : system_tables) + AppendClasses(*table, context.prefix, CompletionSource::kSystem, collected, true); + } + } + else + { + if (editing_table) + AppendClasses(*editing_table, context.prefix, CompletionSource::kEditing, collected, true); + for (const auto* table : workspace_tables) + AppendClasses(*table, context.prefix, CompletionSource::kWorkspace, collected, true); + for (const auto* table : system_tables) + AppendClasses(*table, context.prefix, CompletionSource::kSystem, collected, true); + } } } else if (context.is_createobject_context) { - if (editing_table) - AppendCreateObjectClasses(*editing_table, context.prefix, CompletionSource::kEditing, collected, context.createobject_has_open_quote, context.createobject_quote); - for (const auto* table : workspace_tables) - AppendCreateObjectClasses(*table, context.prefix, CompletionSource::kWorkspace, collected, context.createobject_has_open_quote, context.createobject_quote); - for (const auto* table : system_tables) - AppendCreateObjectClasses(*table, context.prefix, CompletionSource::kSystem, collected, context.createobject_has_open_quote, context.createobject_quote); + if (auto qualified = ParseUnitQualifiedPrefix(context.prefix)) + { + if (editing_table) + AppendCreateObjectClasses(*editing_table, qualified->member_prefix, CompletionSource::kEditing, collected, context.createobject_has_open_quote, context.createobject_quote, qualified->unit_name); + for (const auto* table : workspace_tables) + AppendCreateObjectClasses(*table, qualified->member_prefix, CompletionSource::kWorkspace, collected, context.createobject_has_open_quote, context.createobject_quote, qualified->unit_name); + for (const auto* table : system_tables) + AppendCreateObjectClasses(*table, qualified->member_prefix, CompletionSource::kSystem, collected, context.createobject_has_open_quote, context.createobject_quote, qualified->unit_name); + } + else if (editing_table && content) + { + auto unit_order = CollectUnitSearchOrder(*editing_table, params.position, *content); + if (!unit_order.empty()) + { + for (const auto& unit_name : unit_order) + { + AppendCreateObjectClasses(*editing_table, context.prefix, CompletionSource::kEditing, collected, context.createobject_has_open_quote, context.createobject_quote, unit_name); + for (const auto* table : workspace_tables) + AppendCreateObjectClasses(*table, context.prefix, CompletionSource::kWorkspace, collected, context.createobject_has_open_quote, context.createobject_quote, unit_name); + for (const auto* table : system_tables) + AppendCreateObjectClasses(*table, context.prefix, CompletionSource::kSystem, collected, context.createobject_has_open_quote, context.createobject_quote, unit_name); + } + } + else + { + AppendCreateObjectClasses(*editing_table, context.prefix, CompletionSource::kEditing, collected, context.createobject_has_open_quote, context.createobject_quote); + for (const auto* table : workspace_tables) + AppendCreateObjectClasses(*table, context.prefix, CompletionSource::kWorkspace, collected, context.createobject_has_open_quote, context.createobject_quote); + for (const auto* table : system_tables) + AppendCreateObjectClasses(*table, context.prefix, CompletionSource::kSystem, collected, context.createobject_has_open_quote, context.createobject_quote); + } + } + else + { + if (editing_table) + AppendCreateObjectClasses(*editing_table, context.prefix, CompletionSource::kEditing, collected, context.createobject_has_open_quote, context.createobject_quote); + for (const auto* table : workspace_tables) + AppendCreateObjectClasses(*table, context.prefix, CompletionSource::kWorkspace, collected, context.createobject_has_open_quote, context.createobject_quote); + for (const auto* table : system_tables) + AppendCreateObjectClasses(*table, context.prefix, CompletionSource::kSystem, collected, context.createobject_has_open_quote, context.createobject_quote); + } } else if (context.is_unit_context) {