@@ -37,10 +37,25 @@ pub fn resolve_real_module(db: &dyn Db, module_name: &ModuleName) -> Option<Modu
37
37
/// Which files should be visible when doing a module query
38
38
#[ derive( Debug , Copy , Clone , PartialEq , Eq , PartialOrd , Ord , Hash ) ]
39
39
pub ( crate ) enum ModuleResolveMode {
40
+ /// Stubs are allowed to appear.
41
+ ///
42
+ /// This is the "normal" mode almost everything uses, as type checkers are in fact supposed
43
+ /// to *prefer* stubs over the actual implementations.
40
44
StubsAllowed ,
45
+ /// Stubs are not allowed to appear.
46
+ ///
47
+ /// This is the "goto definition" mode, where we need to ignore the typing spec and find actual
48
+ /// implementations. When querying searchpaths this also notably replaces typeshed with
49
+ /// the "real" stdlib.
41
50
StubsNotAllowed ,
42
51
}
43
52
53
+ #[ salsa:: interned]
54
+ #[ derive( Debug ) ]
55
+ pub ( crate ) struct ModuleResolveModeIngredient < ' db > {
56
+ mode : ModuleResolveMode ,
57
+ }
58
+
44
59
impl ModuleResolveMode {
45
60
fn stubs_allowed ( self ) -> bool {
46
61
matches ! ( self , Self :: StubsAllowed )
@@ -108,7 +123,7 @@ pub(crate) fn file_to_module(db: &dyn Db, file: File) -> Option<Module> {
108
123
109
124
let path = SystemOrVendoredPathRef :: try_from_file ( db, file) ?;
110
125
111
- let module_name = search_paths ( db) . find_map ( |candidate| {
126
+ let module_name = search_paths ( db, ModuleResolveMode :: StubsAllowed ) . find_map ( |candidate| {
112
127
let relative_path = match path {
113
128
SystemOrVendoredPathRef :: System ( path) => candidate. relativize_system_path ( path) ,
114
129
SystemOrVendoredPathRef :: Vendored ( path) => candidate. relativize_vendored_path ( path) ,
@@ -137,8 +152,8 @@ pub(crate) fn file_to_module(db: &dyn Db, file: File) -> Option<Module> {
137
152
}
138
153
}
139
154
140
- pub ( crate ) fn search_paths ( db : & dyn Db ) -> SearchPathIterator {
141
- Program :: get ( db) . search_paths ( db) . iter ( db)
155
+ pub ( crate ) fn search_paths ( db : & dyn Db , resolve_mode : ModuleResolveMode ) -> SearchPathIterator {
156
+ Program :: get ( db) . search_paths ( db) . iter ( db, resolve_mode )
142
157
}
143
158
144
159
#[ derive( Clone , Debug , PartialEq , Eq ) ]
@@ -147,7 +162,16 @@ pub struct SearchPaths {
147
162
/// These shouldn't ever change unless the config settings themselves change.
148
163
static_paths : Vec < SearchPath > ,
149
164
150
- /// site-packages paths are not included in the above field:
165
+ /// Path to typeshed, which should come immediately after static paths.
166
+ ///
167
+ /// This can currently only be None if the `SystemPath` this points to is already in `static_paths`.
168
+ stdlib_path : Option < SearchPath > ,
169
+
170
+ /// Path to the real stdlib, this replaces typeshed (`stdlib_path`) for goto-definition searches
171
+ /// ([`ModuleResolveMode::StubsNotAllowed`]).
172
+ real_stdlib_path : Option < SearchPath > ,
173
+
174
+ /// site-packages paths are not included in the above fields:
151
175
/// if there are multiple site-packages paths, editable installations can appear
152
176
/// *between* the site-packages paths on `sys.path` at runtime.
153
177
/// That means we can't know where a second or third `site-packages` path should sit
@@ -156,8 +180,6 @@ pub struct SearchPaths {
156
180
site_packages : Vec < SearchPath > ,
157
181
158
182
typeshed_versions : TypeshedVersions ,
159
-
160
- real_stdlib_path : Option < SearchPath > ,
161
183
}
162
184
163
185
impl SearchPaths {
@@ -226,7 +248,11 @@ impl SearchPaths {
226
248
)
227
249
} ;
228
250
229
- static_paths. push ( stdlib_path) ;
251
+ let real_stdlib_path = if let Some ( path) = real_stdlib_path {
252
+ Some ( SearchPath :: real_stdlib ( system, path. clone ( ) ) ?)
253
+ } else {
254
+ None
255
+ } ;
230
256
231
257
let mut site_packages: Vec < _ > = Vec :: with_capacity ( site_packages_paths. len ( ) ) ;
232
258
@@ -254,17 +280,39 @@ impl SearchPaths {
254
280
}
255
281
} ) ;
256
282
257
- let real_stdlib_path = if let Some ( path) = real_stdlib_path {
258
- Some ( SearchPath :: real_stdlib ( system, path. clone ( ) ) ?)
283
+ // Users probably shouldn't do this but... if they've shadowed their stdlib we should deduplicate it away.
284
+ // This notably will mess up anything that checks if a search path "is the standard library" as we won't
285
+ // "remember" that fact for static paths.
286
+ //
287
+ // (We used to shove these into static_paths, so the above retain implicitly did this. I am opting to
288
+ // preserve this behaviour to avoid getting into the weeds of corner cases.)
289
+ let stdlib_path_is_shadowed = stdlib_path
290
+ . as_system_path ( )
291
+ . map ( |path| seen_paths. contains ( path) )
292
+ . unwrap_or ( false ) ;
293
+ let real_stdlib_path_is_shadowed = real_stdlib_path
294
+ . as_ref ( )
295
+ . and_then ( SearchPath :: as_system_path)
296
+ . map ( |path| seen_paths. contains ( path) )
297
+ . unwrap_or ( false ) ;
298
+
299
+ let stdlib_path = if stdlib_path_is_shadowed {
300
+ None
259
301
} else {
302
+ Some ( stdlib_path)
303
+ } ;
304
+ let real_stdlib_path = if real_stdlib_path_is_shadowed {
260
305
None
306
+ } else {
307
+ real_stdlib_path
261
308
} ;
262
309
263
310
Ok ( SearchPaths {
264
311
static_paths,
312
+ stdlib_path,
313
+ real_stdlib_path,
265
314
site_packages,
266
315
typeshed_versions,
267
- real_stdlib_path,
268
316
} )
269
317
}
270
318
@@ -279,22 +327,32 @@ impl SearchPaths {
279
327
}
280
328
}
281
329
282
- pub ( super ) fn iter < ' a > ( & ' a self , db : & ' a dyn Db ) -> SearchPathIterator < ' a > {
330
+ pub ( super ) fn iter < ' a > (
331
+ & ' a self ,
332
+ db : & ' a dyn Db ,
333
+ mode : ModuleResolveMode ,
334
+ ) -> SearchPathIterator < ' a > {
335
+ let stdlib_path = self . stdlib ( mode) ;
283
336
SearchPathIterator {
284
337
db,
285
338
static_paths : self . static_paths . iter ( ) ,
339
+ stdlib_path,
286
340
dynamic_paths : None ,
341
+ mode : ModuleResolveModeIngredient :: new ( db, mode) ,
342
+ }
343
+ }
344
+
345
+ pub ( crate ) fn stdlib ( & self , mode : ModuleResolveMode ) -> Option < & SearchPath > {
346
+ match mode {
347
+ ModuleResolveMode :: StubsAllowed => self . stdlib_path . as_ref ( ) ,
348
+ ModuleResolveMode :: StubsNotAllowed => self . real_stdlib_path . as_ref ( ) ,
287
349
}
288
350
}
289
351
290
352
pub ( crate ) fn custom_stdlib ( & self ) -> Option < & SystemPath > {
291
- self . static_paths . iter ( ) . find_map ( |search_path| {
292
- if search_path. is_standard_library ( ) {
293
- search_path. as_system_path ( )
294
- } else {
295
- None
296
- }
297
- } )
353
+ self . stdlib_path
354
+ . as_ref ( )
355
+ . and_then ( SearchPath :: as_system_path)
298
356
}
299
357
300
358
pub ( crate ) fn typeshed_versions ( & self ) -> & TypeshedVersions {
@@ -311,11 +369,15 @@ impl SearchPaths {
311
369
/// should come between the two `site-packages` directories when it comes to
312
370
/// module-resolution priority.
313
371
#[ salsa:: tracked( returns( deref) , heap_size=get_size2:: GetSize :: get_heap_size) ]
314
- pub ( crate ) fn dynamic_resolution_paths ( db : & dyn Db ) -> Vec < SearchPath > {
372
+ pub ( crate ) fn dynamic_resolution_paths < ' db > (
373
+ db : & ' db dyn Db ,
374
+ mode : ModuleResolveModeIngredient < ' db > ,
375
+ ) -> Vec < SearchPath > {
315
376
tracing:: debug!( "Resolving dynamic module resolution paths" ) ;
316
377
317
378
let SearchPaths {
318
379
static_paths,
380
+ stdlib_path,
319
381
site_packages,
320
382
typeshed_versions : _,
321
383
real_stdlib_path,
@@ -333,6 +395,15 @@ pub(crate) fn dynamic_resolution_paths(db: &dyn Db) -> Vec<SearchPath> {
333
395
. map ( Cow :: Borrowed )
334
396
. collect ( ) ;
335
397
398
+ // Use the `ModuleResolveMode` to determine which stdlib (if any) to mark as existing
399
+ let stdlib = match mode. mode ( db) {
400
+ ModuleResolveMode :: StubsAllowed => stdlib_path,
401
+ ModuleResolveMode :: StubsNotAllowed => real_stdlib_path,
402
+ } ;
403
+ if let Some ( path) = stdlib. as_ref ( ) . and_then ( SearchPath :: as_system_path) {
404
+ existing_paths. insert ( Cow :: Borrowed ( path) ) ;
405
+ }
406
+
336
407
let files = db. files ( ) ;
337
408
let system = db. system ( ) ;
338
409
@@ -405,15 +476,6 @@ pub(crate) fn dynamic_resolution_paths(db: &dyn Db) -> Vec<SearchPath> {
405
476
}
406
477
}
407
478
408
- // Append the real stdlib as the very last option in search.
409
- // Normally this means it will always be shadowed by typeshed.
410
- //
411
- // FIXME(Gankra): ideally this should be completely disabled unless we're in
412
- // `ModuleResolveMode::NoStubsAllowed`.
413
- if let Some ( real_stdlib_path) = real_stdlib_path {
414
- dynamic_paths. push ( real_stdlib_path. clone ( ) ) ;
415
- }
416
-
417
479
dynamic_paths
418
480
}
419
481
@@ -427,7 +489,9 @@ pub(crate) fn dynamic_resolution_paths(db: &dyn Db) -> Vec<SearchPath> {
427
489
pub ( crate ) struct SearchPathIterator < ' db > {
428
490
db : & ' db dyn Db ,
429
491
static_paths : std:: slice:: Iter < ' db , SearchPath > ,
492
+ stdlib_path : Option < & ' db SearchPath > ,
430
493
dynamic_paths : Option < std:: slice:: Iter < ' db , SearchPath > > ,
494
+ mode : ModuleResolveModeIngredient < ' db > ,
431
495
}
432
496
433
497
impl < ' db > Iterator for SearchPathIterator < ' db > {
@@ -437,14 +501,19 @@ impl<'db> Iterator for SearchPathIterator<'db> {
437
501
let SearchPathIterator {
438
502
db,
439
503
static_paths,
504
+ stdlib_path,
505
+ mode,
440
506
dynamic_paths,
441
507
} = self ;
442
508
443
- static_paths. next ( ) . or_else ( || {
444
- dynamic_paths
445
- . get_or_insert_with ( || dynamic_resolution_paths ( * db) . iter ( ) )
446
- . next ( )
447
- } )
509
+ static_paths
510
+ . next ( )
511
+ . or_else ( || stdlib_path. take ( ) )
512
+ . or_else ( || {
513
+ dynamic_paths
514
+ . get_or_insert_with ( || dynamic_resolution_paths ( * db, * mode) . iter ( ) )
515
+ . next ( )
516
+ } )
448
517
}
449
518
}
450
519
@@ -581,7 +650,7 @@ fn resolve_name(db: &dyn Db, name: &ModuleName, mode: ModuleResolveMode) -> Opti
581
650
let stub_name = name. to_stub_package ( ) ;
582
651
let mut is_namespace_package = false ;
583
652
584
- for search_path in search_paths ( db) {
653
+ for search_path in search_paths ( db, mode ) {
585
654
// When a builtin module is imported, standard module resolution is bypassed:
586
655
// the module name always resolves to the stdlib module,
587
656
// even if there's a module of the same name in the first-party root
@@ -932,9 +1001,7 @@ mod tests {
932
1001
use ruff_db:: Db ;
933
1002
use ruff_db:: files:: { File , FilePath , system_path_to_file} ;
934
1003
use ruff_db:: system:: { DbWithTestSystem as _, DbWithWritableSystem as _} ;
935
- use ruff_db:: testing:: {
936
- assert_const_function_query_was_not_run, assert_function_query_was_not_run,
937
- } ;
1004
+ use ruff_db:: testing:: assert_function_query_was_not_run;
938
1005
use ruff_python_ast:: PythonVersion ;
939
1006
940
1007
use crate :: db:: tests:: TestDb ;
@@ -1848,7 +1915,12 @@ not_a_directory
1848
1915
& FilePath :: system( "/y/src/bar.py" )
1849
1916
) ;
1850
1917
let events = db. take_salsa_events ( ) ;
1851
- assert_const_function_query_was_not_run ( & db, dynamic_resolution_paths, & events) ;
1918
+ assert_function_query_was_not_run (
1919
+ & db,
1920
+ dynamic_resolution_paths,
1921
+ ModuleResolveModeIngredient :: new ( & db, ModuleResolveMode :: StubsAllowed ) ,
1922
+ & events,
1923
+ ) ;
1852
1924
}
1853
1925
1854
1926
#[ test]
@@ -1917,7 +1989,8 @@ not_a_directory
1917
1989
. with_site_packages_files ( & [ ( "_foo.pth" , "/src" ) ] )
1918
1990
. build ( ) ;
1919
1991
1920
- let search_paths: Vec < & SearchPath > = search_paths ( & db) . collect ( ) ;
1992
+ let search_paths: Vec < & SearchPath > =
1993
+ search_paths ( & db, ModuleResolveMode :: StubsAllowed ) . collect ( ) ;
1921
1994
1922
1995
assert ! ( search_paths. contains(
1923
1996
&&SearchPath :: first_party( db. system( ) , SystemPathBuf :: from( "/src" ) ) . unwrap( )
0 commit comments