Skip to content

Conversation

@DanilaFe
Copy link
Contributor

Consider the program:

module Lib {
  proc outermost(x) {
    compilerWarning("In outermost with argument of type " + x.type:string);
  }
}

module A {
  proc a(x) {
    outermost(x);
  }
}
module B {
  private use C;
  import A.a;

  proc b(x) do a(x);
}

module C {
  private use Lib;
  import B.b;

  proc c(x) do b(x);
}

module poichain {
  import C.c;

  record R {}

  proc main() {
    c(new R());
  }
}

The three->way chain is required, because this bug is about collapsing POI scopes.

Basically, by POI, this program resolves: a(...) is resolved in the scope of b(…), which is resolved in the scope of c(…), which has a private use Lib. Thus, a(…) has access to outermost(…).

However, we try to avoid building up long chains of POI scopes (a-in-scope-of-b-in-scope-of-c). As an optimization, if one module uses another, and both are in the POI chain, we remove the usee from the POI chain, since anything in its scope will be found via the use. In this case, B uses C, so we remove C from the POI scope lookup chain. Unfortunately, this is not sound, as C has private use of Lib, which does not propagate through use C. As a result, the call to a misses outermost.

This PR adjusts the logic of isWholeScopeVisibleFromScope, which is what used to implement the optimization. In particular, I found it to have two odd behaviors

  • the private use issue outlined above: not caring about definitions brought in privately (and thus, not re-exported)
  • assuming child modules re-export parent modules' symbols. Eg., use MParent.MChild, where MParent had a public use Lib, was treated as finding Lib. However, I don't believe this is accurate. See:
    module A {
      var x = 42;
      module B {
      }
    }
    module C {
      use A.B;
    
      proc main() {
        writeln(x);
      }
    }

This PR reimplements isWholeScopeVisibleFromScope from first principles. I consider two ways for one scope to be wholly visible from another:

  1. A child scope nested in an outer scope can see all of the variables in the outer scope, even if they are private.
  2. Any scope that is within reach of a use M can see all of M but only if M doesn't have private definitions. For example, if M has a private proc, use M will not bring that in, and thus, it's not true that the scope of M is "fully visible" from the user.
  • The use M can be transitive; either there's a use M right in the scope, OR there's a use Intermediate, where Intermediate has a public use M, OR Intermediate has a public use OtherIntermediate which has a public use M.... Note that except for the first use, all uses must be public.

To help reduce the impact of this method, I kept it conservative. As a result, I expect isWholeScopeVisibleFromScope to now be stricter than it used to be (certainly it is in the buggy case). This technically expanded existing POI chains in some cases and might've affected caching of instantiations. However, I observed no noticeable performance impact from that, either.

Testing

  • new dyno test

Signed-off-by: Danila Fedorin <[email protected]>
Encountering the same candidate N times through a PoI scope
should not baloon the number of reported rejected candidates.

Signed-off-by: Danila Fedorin <[email protected]>
Signed-off-by: Danila Fedorin <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant