@@ -95,6 +95,19 @@ signature module LocalNameBindingInputSig<LocationSig Location> {
9595 */
9696 predicate declInScope ( AstNode definingNode , string name , AstNode scope ) ;
9797
98+ /**
99+ * Holds if it is uncertain whether the reference to local declaration named
100+ * `name` at `definingNode` in scope `scope` is a declaration or an access.
101+ *
102+ * For example, in Ruby variables are not explicitly declared, so we consider
103+ * the first mention of a variable to be its declaration.
104+ *
105+ * This predicate must be a subset of `declInScope`.
106+ */
107+ default predicate declInScopeIsUncertain ( AstNode definingNode , string name , AstNode scope ) {
108+ none ( )
109+ }
110+
98111 /**
99112 * Holds if a local declaration named `name` is implicitly in scope in the given `scope`.
100113 */
@@ -123,6 +136,12 @@ signature module LocalNameBindingInputSig<LocationSig Location> {
123136 * full control of scope resolution for for specific types of references.
124137 */
125138 default predicate lookupStartsAt ( AstNode n , AstNode scope ) { none ( ) }
139+
140+ /**
141+ * Holds if `access` is valid access of the local declared at `definingNode`.
142+ */
143+ bindingset [ access, definingNode]
144+ default predicate isValidAccess ( AstNode access , AstNode definingNode ) { any ( ) }
126145}
127146
128147/**
@@ -299,6 +318,12 @@ module LocalNameBinding<LocationSig Location, LocalNameBindingInputSig<Location>
299318 not lookupStartsAt ( n , _) and
300319 lookup = getEnclosingScope ( n )
301320 )
321+ or
322+ exists ( Scope scope |
323+ declInScopeIsUncertain ( n , name , scope ) and
324+ not isTopScope ( scope ) and
325+ lookup = getEnclosingScope ( scope )
326+ )
302327 }
303328
304329 pragma [ nomagic]
@@ -308,7 +333,11 @@ module LocalNameBinding<LocationSig Location, LocalNameBindingInputSig<Location>
308333 or
309334 exists ( Scope mid |
310335 lookupInScope ( name , lookup , mid ) and
311- not declInScope ( _, name , mid ) and
336+ not exists ( AstNode definingNode |
337+ declInScope ( definingNode , name , mid ) and
338+ // allow to step through uncertain declarations
339+ not declInScopeIsUncertain ( definingNode , name , mid )
340+ ) and
312341 not implicitDeclInScope ( name , mid ) and
313342 not isTopScope ( mid ) and
314343 scope = getEnclosingScope ( mid )
@@ -352,7 +381,15 @@ module LocalNameBinding<LocationSig Location, LocalNameBindingInputSig<Location>
352381 private string name ;
353382 private AstNode scope ;
354383
355- ExplicitLocal ( ) { this = TExplicitLocal ( definingNode , name , scope ) }
384+ cached
385+ ExplicitLocal ( ) {
386+ CachedStage:: ref ( ) and
387+ this = TExplicitLocal ( definingNode , name , scope ) and
388+ // skip uncertain declarations that are in fact accesses
389+ not exists ( AstNode other |
390+ accessStep ( definingNode , TExplicitLocal ( other , _, _) ) and other != definingNode
391+ )
392+ }
356393
357394 override AstNode getDefiningNode ( ) { result = definingNode }
358395
@@ -380,27 +417,47 @@ module LocalNameBinding<LocationSig Location, LocalNameBindingInputSig<Location>
380417 }
381418
382419 pragma [ nomagic]
383- private predicate resolveInScope ( string name , Scope lookup , Local l ) {
420+ private predicate resolveInScope ( string name , Scope lookup , TLocal l ) {
384421 exists ( Scope scope | lookupInScope ( name , lookup , scope ) |
385422 l = TExplicitLocal ( _, name , scope ) or
386423 l = TImplicitLocal ( name , scope )
387424 )
388425 }
389426
390- cached
391- private predicate access ( AstNode access , Local l ) {
427+ pragma [ nomagic ]
428+ private predicate accessStep ( AstNode access , TLocal l ) {
392429 CachedStage:: ref ( ) and
393430 exists ( Scope lookup , string name |
394431 accessCandInLookupScope ( access , name , lookup ) and
395432 resolveInScope ( name , lookup , l )
433+ |
434+ l instanceof TImplicitLocal
435+ or
436+ l = TExplicitLocal ( any ( AstNode definingNode | isValidAccess ( access , definingNode ) ) , _, _)
437+ )
438+ }
439+
440+ pragma [ nomagic]
441+ private predicate accessSteps ( AstNode access , Local l ) {
442+ accessStep ( access , l )
443+ or
444+ exists ( AstNode mid , string name , AstNode scope |
445+ accessSteps ( mid , l ) and
446+ accessStep ( access , TExplicitLocal ( mid , name , scope ) ) and
447+ declInScopeIsUncertain ( mid , name , scope )
396448 )
397449 }
398450
399451 /** A local access. */
400452 final class LocalAccess extends AstNodeFinal {
401453 private Local l ;
402454
403- LocalAccess ( ) { access ( this , l ) }
455+ cached
456+ LocalAccess ( ) {
457+ CachedStage:: ref ( ) and
458+ accessSteps ( this , l ) and
459+ accessCand ( this , _)
460+ }
404461
405462 /** Gets the local entity being accessed. */
406463 Local getLocal ( ) { result = l }
0 commit comments