Skip to content

Commit 1d4ba6b

Browse files
committed
perf(robot): speed up namespace cache loading with source hints and top-level-only imports
Add resolved_resource_sources to NamespaceData that maps import hint keys to resolved file paths. During from_data() re-resolution, these hints skip expensive find_resource() filesystem searches when the cached path still exists on disk. Also limit cached imports to top-level only (recursive imports are re-discovered during resolution), replace Path-based normalization with string-only os.path calls in imports_manager, and add a hint_key property to Import for consistent key computation.
1 parent 05142c8 commit 1d4ba6b

File tree

4 files changed

+36
-6
lines changed

4 files changed

+36
-6
lines changed

packages/robot/src/robotcode/robot/diagnostics/entities.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ class Import(SourceEntity):
5252
name: Optional[str]
5353
name_token: Optional[Token]
5454

55+
@property
56+
def hint_key(self) -> str:
57+
return f"{self.name}\0{self.source or ''}\0{self.line_no}"
58+
5559
@property
5660
def range(self) -> Range:
5761
return Range(

packages/robot/src/robotcode/robot/diagnostics/import_resolver.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
containing all resolved entries and collected diagnostics.
1010
"""
1111

12+
import os
1213
from dataclasses import dataclass, field
1314
from pathlib import Path
1415
from typing import Any, Dict, Iterable, List, Optional, Union
@@ -85,6 +86,7 @@ class ImportResolver:
8586
"_scope",
8687
"_sentinel",
8788
"_source",
89+
"_source_hints",
8890
"_source_id",
8991
"_variables",
9092
"_variables_dirty",
@@ -112,9 +114,12 @@ def __init__(
112114
self._diagnostics: List[Diagnostic] = []
113115
self._variables: Dict[str, Any] = {}
114116
self._variables_dirty = True
117+
self._source_hints: Dict[str, str] = {}
115118

116-
def resolve(self, imports: Iterable[Import]) -> ResolvedImports:
119+
def resolve(self, imports: Iterable[Import], *, source_hints: Optional[Dict[str, str]] = None) -> ResolvedImports:
117120
"""Resolve all imports and return the result."""
121+
if source_hints:
122+
self._source_hints = source_hints
118123
self._refresh_variables()
119124
self._import_default_libraries()
120125
self._handle_imports(imports, self._base_dir, top_level=True)
@@ -395,11 +400,19 @@ def _import_resource(
395400
) -> None:
396401
assert imp.name is not None
397402

403+
# Look up cached source hint to skip find_resource() filesystem search
404+
source_hint: Optional[str] = None
405+
if self._source_hints:
406+
hint = self._source_hints.get(imp.hint_key)
407+
if hint is not None and os.path.isfile(hint):
408+
source_hint = hint
409+
398410
resource_doc = self._imports_manager.get_resource_doc_for_resource_import(
399411
imp.name,
400412
base_dir,
401413
sentinel=self._sentinel,
402414
variables=self._variables,
415+
source=source_hint,
403416
)
404417
entry = ResourceEntry(
405418
name=resource_doc.name,

packages/robot/src/robotcode/robot/diagnostics/imports_manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1852,13 +1852,13 @@ def _get_entry_for_resource_import(
18521852
source: Optional[str] = None,
18531853
) -> _ResourcesEntry:
18541854
source = source or self.find_resource(name, base_dir, variables=variables)
1855-
source_path = normalized_path(Path(source))
1855+
normalized_source = os.path.normpath(os.path.abspath(source))
18561856

1857-
entry_key = _ResourcesEntryKey(str(source_path))
1857+
entry_key = _ResourcesEntryKey(normalized_source)
18581858

18591859
with self._resources_lock:
18601860
if entry_key not in self._resources:
1861-
self._resources[entry_key] = _ResourcesEntry(name, self, source_path)
1861+
self._resources[entry_key] = _ResourcesEntry(name, self, Path(normalized_source))
18621862

18631863
entry = self._resources[entry_key]
18641864

packages/robot/src/robotcode/robot/diagnostics/namespace.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ class NamespaceData:
126126
# --- ScopeTree (local scopes only, file_scope is reconstructed) ---
127127
local_scopes: List[LocalScope] = field(default_factory=list)
128128

129+
# --- Resolved resource import sources ---
130+
# Maps (import_name\0source_file) → resolved absolute path for resources.
131+
# Used as source hints in from_data() to skip find_resource() filesystem
132+
# lookups during import re-resolution.
133+
resolved_resource_sources: Dict[str, str] = field(default_factory=dict)
134+
129135
# --- Authoritative variable definitions (stable_id → VariableDefinition) ---
130136
# Stores the exact VariableDefinition objects that were referenced during
131137
# analysis. Used as fallback in from_data() when the re-parsed resource doc
@@ -494,13 +500,19 @@ def to_data(self) -> NamespaceData:
494500
else:
495501
var_assigns_merged[sid] = set(ranges)
496502

503+
# Build resolved resource source hints for from_data() optimization
504+
resolved_res_sources: Dict[str, str] = {}
505+
for imp, entry in self._import_entries.items():
506+
if isinstance(entry, ResourceEntry) and entry.library_doc.source:
507+
resolved_res_sources[imp.hint_key] = entry.library_doc.source
508+
497509
return NamespaceData(
498510
source=self.source,
499511
source_id=str(self.source_id) if self.source_id else None,
500512
document_type=self.document_type.value if self.document_type else None,
501513
languages=self.languages,
502514
workspace_languages=self.workspace_languages,
503-
imports=list(self._import_entries.keys()),
515+
imports=[imp for imp in self._import_entries if imp.source is None or imp.source == self.source],
504516
diagnostics=list(self._diagnostics),
505517
test_case_definitions=list(self._test_case_definitions),
506518
keyword_references=kw_refs_merged,
@@ -511,6 +523,7 @@ def to_data(self) -> NamespaceData:
511523
testcase_tag_references={k: set(v) for k, v in self._testcase_tag_references.items()},
512524
metadata_references={k: set(v) for k, v in self._metadata_references.items()},
513525
local_scopes=list(self._scope_tree.local_scopes),
526+
resolved_resource_sources=resolved_res_sources,
514527
variable_definitions=all_var_defs,
515528
)
516529

@@ -542,7 +555,7 @@ def from_data(
542555
)
543556

544557
resolver = ImportResolver(imports_manager, data.source, scope, sentinel=sentinel)
545-
resolved = resolver.resolve(data.imports)
558+
resolved = resolver.resolve(data.imports, source_hints=data.resolved_resource_sources)
546559

547560
# Add imported variables to scope (needed for variable lookup)
548561
for resource_entry in resolved.resources.values():

0 commit comments

Comments
 (0)