Skip to content

Commit 7bfd652

Browse files
committed
Use new scope_is_eager() function from rebase
1 parent c809cfc commit 7bfd652

6 files changed

Lines changed: 64 additions & 38 deletions

File tree

crates/oak_db/src/file_imports.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,10 @@ impl File {
283283
let (collation, attaches) = if index.scope_is_eager(cursor_scope) {
284284
(CollationView::Eager, AttachView::Eager(offset))
285285
} else {
286-
(CollationView::Lazy, AttachView::Lazy(offset))
286+
(CollationView::Lazy, AttachView::Lazy {
287+
offset,
288+
scope_id: cursor_scope,
289+
})
287290
};
288291

289292
let own = self.attach_layers(db, attaches);

crates/oak_db/src/file_resolve.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,14 @@ impl<'db> File {
105105
}
106106

107107
// Nothing local reaches the use, so resolve across files.
108-
let file_scope = ScopeId::from(0);
109-
if use_scope != file_scope {
110-
// Function body: the lazy / end-of-file view the body sees at run time.
108+
if !index.scope_is_eager(use_scope) {
109+
// Lazy body: the end-of-file view it sees when it actually runs.
111110
return self.resolve(db, name);
112111
}
113112

114-
// Top level: collation predecessors / other visible files (exports-only
115-
// chase, same as `resolve`'s per-context walk). Avoids the sibling
116-
// cycle and matches R's namespace semantics.
113+
// Eager scope: collation predecessors / other visible files
114+
// (exports-only chase, same as `resolve`'s per-context walk). Avoids
115+
// the sibling cycle and matches R's namespace semantics.
117116
resolve_per_sourcing_file(db, &self.imports_by_sourcing_file_at(db, offset), name)
118117
}
119118

crates/oak_db/src/tests/file_resolve_at.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,44 @@ fn test_conditional_library_resolves_only_inside_its_branch() {
590590
assert!(script.resolve_at(&db, after).is_empty());
591591
}
592592

593+
#[test]
594+
fn test_local_block_does_not_see_a_library_call_after_it() {
595+
// `local()` runs at its call site, so a cursor inside it sees the search
596+
// path as of that point: `library(mypkg)` hasn't run yet, so `foo` isn't
597+
// attached.
598+
let mut db = TestDb::new();
599+
install_library_package(&mut db, "mypkg", &["foo"], &[(
600+
"library/mypkg/R/a.R",
601+
"foo <- function() 42\n",
602+
)]);
603+
604+
let (_ws_root, files) = setup_workspace_scripts(&mut db, "ws", &[(
605+
"ws/script.R",
606+
"local({\n foo\n})\nlibrary(mypkg)\n",
607+
)]);
608+
let script = files[0];
609+
let source = script.source_text(&db).clone();
610+
611+
let offset = TextSize::from(source.find(" foo").unwrap() as u32 + 2);
612+
assert!(script.resolve_at(&db, offset).is_empty());
613+
}
614+
615+
#[test]
616+
fn test_local_block_sees_a_file_scope_binding_before_it() {
617+
// A binding made before the block is already in place by the time
618+
// `local()` runs, same as for a use at file scope.
619+
let mut db = TestDb::new();
620+
let source = "x <- 1\nlocal({\n x\n})\n";
621+
let file = make_file(&mut db, "a.R", source);
622+
623+
let offset = TextSize::from(source.rfind('x').unwrap() as u32);
624+
let def = resolve_one(&db, file, offset);
625+
626+
assert_eq!(def.file(&db), file);
627+
let range = def.name_range(&db).expect("local has a name range");
628+
assert_eq!(usize::from(range.start()), 0);
629+
}
630+
593631
/// A workspace holding `main.R`, which sources `helpers.R`, plus whatever else
594632
/// the caller lists. Returns the files in the given order.
595633
fn setup_sourced(db: &mut TestDb, files: &[(&str, &str)]) -> Vec<File> {

crates/oak_semantic/src/builder.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,20 @@ impl<R: ImportsResolver> SemanticIndexBuilder<R> {
294294
Some(scope)
295295
}
296296

297+
/// The scan unit that controls when code in `scope` runs: that scope itself
298+
/// when lazy, otherwise its nearest lazy ancestor. `None` means the code
299+
/// runs while the file loads.
300+
///
301+
/// Mid-build twin of [`SemanticIndex::enclosing_lazy_scope`], reading the
302+
/// arena the walk is still filling in.
303+
fn enclosing_lazy_scope(&self, scope: ScopeId) -> Option<ScopeId> {
304+
let mut current = scope;
305+
while !self.scopes[current].kind.is_lazy() {
306+
current = self.scopes[current].parent?;
307+
}
308+
Some(current)
309+
}
310+
297311
/// Whether `scope` binds `name` anywhere, regardless of flow position: an
298312
/// already-recorded `IS_BOUND` definition or a pre-scanned assignment. The
299313
/// pre-scan covers definitions the walk hasn't reached yet in this scope.

crates/oak_semantic/src/builder/walk.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ use crate::effects::ResolvedArgumentEffect;
3939
use crate::effects::ResolvedArgumentEffects;
4040
use crate::effects::TargetAccess;
4141
use crate::resolver::ImportsResolver;
42-
use crate::semantic_index::scope_is_top_level;
4342
use crate::semantic_index::AttachRegion;
4443
use crate::semantic_index::Definition;
4544
use crate::semantic_index::DefinitionKind;
@@ -563,7 +562,7 @@ impl<R: ImportsResolver> SemanticIndexBuilder<R> {
563562
// run, or might run after the rest of this file, so we record
564563
// nothing there and let the sourced file fall back to whole-file
565564
// exports.
566-
if scope_is_top_level(&self.scopes, self.current_scope) {
565+
if self.enclosing_lazy_scope(self.current_scope).is_none() {
567566
let file_scope = ScopeId::from(0);
568567
let symbols = &self.walk.symbol_tables[file_scope];
569568
let visible = self.walk.use_def_maps[file_scope].bound_names(symbols);

crates/oak_semantic/src/semantic_index.rs

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,8 @@ impl SemanticIndex {
229229
}
230230

231231
/// Whether `scope` runs during the file's own top-level execution, i.e. no
232-
/// enclosing scope is lazy.
232+
/// enclosing scope is lazy. Wider than "is the file scope", since a
233+
/// `local()` or `test_that()` body runs at its call site.
233234
pub fn scope_is_eager(&self, scope_id: ScopeId) -> bool {
234235
self.enclosing_lazy_scope(scope_id).is_none()
235236
}
@@ -262,15 +263,6 @@ impl SemanticIndex {
262263
&self.diagnostics
263264
}
264265

265-
/// Whether code in `scope` runs during a top-level evaluation of the file.
266-
///
267-
/// Wider than "is the file scope": an eager scope like `local()` or
268-
/// `test_that()` body runs at its call site. A single lazy scope anywhere
269-
/// in the parent chain of `scope` makes it lazy.
270-
pub fn is_top_level(&self, scope: ScopeId) -> bool {
271-
scope_is_top_level(&self.scopes, scope)
272-
}
273-
274266
/// The file-scope names bound by the time the `source()` call at `offset`
275267
/// runs, or `None` if that call sits in a lazy context (a function body),
276268
/// where nothing pins down when it runs.
@@ -449,25 +441,6 @@ impl SemanticIndex {
449441
}
450442
}
451443

452-
/// Whether `scope` runs during a top-level evaluation of the file: no scope
453-
/// from `scope` out to the file root is lazy.
454-
///
455-
/// Shared by [`SemanticIndex::is_top_level`] (post-build) and the builder,
456-
/// which needs the same walk over its own scope arena mid-build to decide
457-
/// whether a `source()` call runs at load time.
458-
pub(crate) fn scope_is_top_level(scopes: &IndexVec<ScopeId, Scope>, scope: ScopeId) -> bool {
459-
let mut current = scope;
460-
loop {
461-
if scopes[current].kind.is_lazy() {
462-
return false;
463-
}
464-
match scopes[current].parent {
465-
Some(parent) => current = parent,
466-
None => return true,
467-
}
468-
}
469-
}
470-
471444
/// Key for looking up an enclosing snapshot. Keyed by the nested scope and the
472445
/// `UseId` of the free variable in that scope, so consumers do an O(1) lookup
473446
/// straight from a use without re-walking the ancestor chain.

0 commit comments

Comments
 (0)