Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dyno: handle shadow scopes for 'private use' #21551

Merged
merged 5 commits into from
Feb 9, 2023

Conversation

mppf
Copy link
Member

@mppf mppf commented Feb 8, 2023

This PR updates the dyno scope resolver to follow the current language design rules about shadow scopes. These evolved a bit after the dyno scope resolver was created (see PR #19306). See also this spec section.

To do so:

  • added a field to VisibilitySymbols to indicate which shadow scope (if any) the use/imported symbols go into
  • renamed doLookupInImports to doLookupInImportsAndUses and have it only consider symbols at the current shadow scope level
  • created doLookupInAutoModules (pulled out of doLookupInImports)
  • adjusted doLookupInScope to check the non-shadow scope and then the two shadow scopes
  • adjusted doResolveUseStmt and doResolveImportStmt to provide a shadow scope value when constructing a VisibilitySymbols
  • improved stringify/dump for VisibilitySymbols and ResolvedVisibilityScope
  • added testing for some of these cases to testScopeResolve.cpp

This fixes a number of tests that fail with --dyno (which activates the dyno scope resolver):

modules/shadowing/issue-19167-3-x
modules/shadowing/issue-19167-5-x
modules/shadowing/var-shadows-private-import
modules/shadowing/var-shadows-public-import
modules/shadowing/var-shadows-public-use
visibility/import/rename/same_name_in_mod
visibility/import/rename/same_name_in_mod2

Reviewed by @DanilaFe - thanks!

  • full local testing
  • full local --dyno testing

Copy link
Contributor

@DanilaFe DanilaFe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

private:
const Scope* scope_; // Scope of the Module etc
// This could technically be an ID but basically
// anything we do with it needs a Scope* anyway.
Kind kind_ = SYMBOL_ONLY;
bool isPrivate_ = true;
int8_t shadowScopeLevel_ = REGULAR_SCOPE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why int8_t instead of ShadowScope?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It saves 4 bytes of memory in each instance here due to the way that C / C++ have enums be int and the way that fields are aligned. If it is a 1 byte type, it can be stored next to the bool, instead of on the next 4-byte aligned word.

We could apply this space optimization in many other places in the uAST but somehow it seemed more important to me in this case, where the enum only stores 3 values. Thanks to our efforts at encapsulation, this field is private, so the type of it is not particularly important to most of the code.

if (onlyInnermost && got) return true;
}

// now check shadow scope 2 (only relevant for 'private use')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might these three checks be somewhat elegantly combined into a for-loop for int8_t and VisibilitySymbols::ShadowScope? something like:

for (int8_t i = VisibilitySymbols::REGULAR_SCOPE; i < VisibilitySymbols::SHADOW_SCOPE_TWO; i++) {
  bool got = false;
  if (i == VisibilitySymbols::REGULAR_SCOPE) { /* lookupInScope logic */ }
  if (i == VisibilitySymbols::SHADOW_SCOPE_ONE) { /* auto-use logic */ }
  got |= doLookupInImportsAndUses(context, scope, resolving, r, name,
                                 onlyInnermost, skipPrivateVisibilities,
                                 VisibilitySymbols::SHADOW_SCOPE_TWO,
                                 checkedScopes, result);
  if (onlyInnermost && got) return true;
}

I'm on the fence about this suggestion, but the reason I like it is that it emphasizes the fact that we are searching each of the three shadow scopes in turn, though some have additional things to search.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree that it could be written that way. However, I feel that the comments adequately emphasize that we are searching each in turn (and if you have a suggestion for improving those comments, I'm happy to do so). At the same time, having a for loop that has conditionals to create different behavior for every index visited seems a bit of an anti-pattern as well.

So, let's leave it as-is.


// 'private use' aka just 'use' introduces 2 shadow scopes
// that appear to be between the module and any parent symbols
SHADOW_SCOPE_ONE = 1, // for module contents brought in by a 'private use'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would more descriptive names be better here, like CONTENTS_SHADOW_SCOPE and NAME_SHADOW_SCOPE? It's not like the scopes are numbered in the spec.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it might seem strange that it doesn't just say something like "Private Use Name Shadow Scope" and "Private Use Contents Shadow Scope". But there is a reason that I used these names. (It is debatable, but I have come to like the names I chose in the PR).

Anyway, here is the rationale.

This enum is within VisibilitySymbols. That type is a sort of normalized form for use and import statements. In fact, it does not directly indicate if it is referring to a use or import at all. Instead, it describes the behaviors that are requested. In particular, the Kind enum indicates if it's bringing in all contents or just the symbol etc. Related to that, if we had CONTENTS_SHADOW_SCOPE, that would be redundant with the Kind setting ALL_CONTENTS and it bakes the behavior of private use into the representation of the normalized form.

Instead, it makes more sense to me that the shadow scope names be independent of how private use behaves. The code that creates a VisibilitySymbols for a use or import is responsible for encoding the shadow scope behavior of use and import into this type. I think this approach has better separation of concerns.

However, I understand that a reader of this code might not understand why we have these shadow scopes at all. That is why I put in comments talking about private use. In a way, this is similar to the comments for the Kind elements. (Comparing Kind and ShadowScope, I am noticing that the new enum elements have comments that are not doxygen-ready. I will fix that presently.)

---
Signed-off-by: Michael Ferguson <[email protected]>
@mppf mppf merged commit f02d9c8 into chapel-lang:main Feb 9, 2023
@mppf mppf deleted the dyno-scope-private-use branch February 9, 2023 13:13
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.

2 participants