-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[SR-2209] Add real AccessScope type. #4579
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
Conversation
Thanks, Aleksey! I'll answer your question first and then add some review comments tomorrow morning. The behavior of SE-0025 states that |
// | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This file defines the AccessScope class. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't really add anything beyond the file header (which is also pretty sparse). If there's nothing more useful to say here, you can just remove it.
Sorry to pile a ton of comments on—this overall looks good and is pretty much spot-on the approach I was trying to describe in the bug. Thank you for doing this! |
Thanks Jordan! I was expecting a lot of comments about second C++ code in my life. |
ee987fe
to
49271d8
Compare
const DeclContext *accessScope = nominal->getFormalAccessScope(); | ||
if (accessScope && !accessScope->isModuleContext()) | ||
auto DC = nominal->getFormalAccessScope().getDeclContext(); | ||
if (DC && !DC->isModuleContext()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this code is really saying accessScope.isPrivate() || accessScope.isFileScope()
. That's a little more expressive than checking the associated DC.
This is looking great. A few more comments, and questions on what you think is the best way to handle certain things. |
49271d8
to
f5a619a
Compare
Done! I've desided to stick with Also adding extra logic for Accessibility accessLevel = accessScope.isFileScope()
? Accessibility::FilePrivate
: accessScope.accessibilityForDiagnostics(); This condition appears in all places where we need to emit Add extra parameter to existing Accessibility accessibilityForDiagnostics(bool topLevelPrivateAsFilePrivate = false) const {
...
if (getDeclContext()->isModuleScopeContext()) {
return isPrivate() && !topLevelPrivateAsFilePrivate
? Accessibility::Private
: Accessibility::FilePrivate;
}
...
} or add extra function for this purpose: Accessibility requiredAccessibilityForDiagnostics() const {
return isFileScope() ? Accessibility::FilePrivate : accessibilityForDiagnostics()
} or leave it as is? |
f5a619a
to
493808f
Compare
Aleksey, I'm very sorry for leaving this behind for two months. It probably needs to be rebased at this point, especially given the pile of access changes that went in late in 3.0.1. Would you like to do that or should I? For your most recent question, I think I like the separate function better. I'll do another pass on the code once we get past the rebase step, but I'm pretty happy with it. |
1. Add new AccessScope type that just wraps a plain DeclContext. 2. Propagate it into all uses of "ValueDecl::getFormalAccessScope". 3. Turn all operations that combine access scopes into methods on AccessScope. 4. Add the "private" flag to distinguish "private" from "fileprivate" scope for top-level DeclContext.
7532046
to
1c26dcf
Compare
@jrose-apple No worries, thanks for coming back 😃 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So much "explicit is better than implicit". :-) More comments:
public: | ||
AccessScope(const DeclContext *DC, bool isPrivate = false) | ||
: Value(DC, isPrivate) { | ||
if (!DC || isa<ModuleDecl>(DC)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style nitpick: I think our dominant indentation strategy here is to double-indent the member initializer line and then single-indent the body as usual.
/// Returns true if this is a child scope of the specified other access scope. | ||
/// | ||
/// \see DeclContext::isChildContextOf | ||
bool isChildOf(AccessScope AS) const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe DeclContext::isChildContextOf returns false
if the two are the same, which implies that public->isChildOf(public)
should also return false here.
@@ -32,6 +32,7 @@ | |||
#include "llvm/Support/TrailingObjects.h" | |||
|
|||
namespace swift { | |||
class AccessScope; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're returning by value, a forward-declaration isn't good enough. The header is cheap enough to just include, I think.
(Unless there's a circularity issue that I've forgotten, in which case we should figure out a better way to fix it.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm afraid that's my fix for the circularity, I'll see what can be done.
"|cannot be declared %select{PRIVATE|fileprivate|internal|public}3}2 " | ||
"because its type uses " | ||
"%select{a private|a fileprivate|an internal|PUBLIC}4 type", | ||
(bool, bool, bool, Accessibility, Accessibility)) | ||
(bool, bool, bool, Accessibility, Accessibility, bool)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just seeing this now: please put the new flag directly after the access level it affects (for all of the modified diagnostics). The jump from argument 3 to argument 5 confused me for a bit.
Action walkToTypePre(Type ty) override { | ||
// Assume failure until we post-visit this node. | ||
// This will be correct as long as we don't ever have self-referential | ||
// Types. | ||
auto cached = Cache.find(ty); | ||
if (cached != Cache.end()) { | ||
auto opaqueCached = reinterpret_cast<intptr_t>(cached->second); | ||
RawScopeStack.back() = intersectAccess(RawScopeStack.back(),opaqueCached); | ||
auto last = &RawScopeStack.back(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: I think we'd prefer auto &last = RawScopeStack.back()
or even AccessScope &last = RawScopeStack.back()
, because it won't be reassigned. (Same below.)
const TypeRepr *thisComplainRepr) { | ||
if (!minAccessScope || | ||
typeAccessScope->isChildContextOf(minAccessScope) || | ||
if (typeAccessScope.isChildOf(minAccessScope) || | ||
(!complainRepr && typeAccessScope == minAccessScope)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this one's supposed to be checking for the same DC rather than identical scopes. ("If we can warn about either one, we should pick the one with a TypeRepr.")
ValueDecl *requirement, | ||
ValueDecl *witness, | ||
bool *isSetter) { | ||
*isSetter = false; | ||
|
||
const DeclContext *protoAccessScope = Proto->getFormalAccessScope(DC); | ||
requiredAccessScope = | ||
*requiredAccessScope.intersectWith(Proto->getFormalAccessScope(DC)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra *
here? How did this compile?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is needed here, becauseintersectWith
returns Optional
.
Previously here was an assert for "non-intersecting scopes" case, now it will crash in the same scenario. Should I make an explicit assert?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, right, my bad. (Somehow I misread this as equivalent to requiredAccessScope->intersectWith
.) The explicit assert is probably a good idea just to make it a little faster to track down what happened if it ever breaks.
bool isExplicit = | ||
SD->getAttrs().hasAttribute<AccessibilityAttr>() || | ||
SD->getDeclContext()->getAsProtocolOrProtocolExtensionContext(); | ||
auto diagID = diag::subscript_type_access; | ||
if (downgradeToWarning == DowngradeToWarning::Yes) | ||
diagID = diag::subscript_type_access_warn; | ||
auto subscriptDeclAccess = isExplicit | ||
? SD->getFormalAccess() | ||
: minAccessScope.requiredAccessibilityForDiagnostics(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems fishy to me, because it's doing something different than before. Can you explain why it's correct to get the required access instead of the currently-inferred access? (and above as well)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order to solve this situation.
+subscript (a: PrivateStruct) -> Int { return 0 } // expected-error {{subscript must be declared fileprivate because its index uses a private type}}
-subscript (a: PrivateStruct) -> Int { return 0 } // expected-error {{subscript must be declared fileprivate because its index uses a fileprivate type}}
+subscript (a: Int) -> PrivateStruct { return PrivateStruct() } // expected-error {{subscript must be declared fileprivate because its element type uses a private type}}
-subscript (a: Int) -> PrivateStruct { return PrivateStruct() } // expected-error {{subscript must be declared fileprivate because its element type uses a fileprivate type}}
If the accessibility declaration isn't explicit, minAccessScope
was used as the required access scope for subscript declaration, which leads to this incorrect message - {{subscript must be declared private because its index uses a private type}}
.
Doing this change I tweaked the parameters for the subscript_type_access
diagnostics in such way, so it complained on the inferred accessibility level in explicit case, and suggest required in implicit case:
%select{must be declared %select{...}1 | cannot be declared %select{...}1}0
where 1 parameter
depends on 0 parameter
Am I missing something here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:-/ I guess I'm unhappy because the same diagnostic argument is being used for two different purposes now, which makes me think there should just be two separate diagnostics. We can fix that in a follow-up commit, though.
1c26dcf
to
58dac9d
Compare
58dac9d
to
3419925
Compare
Done! |
@swift-ci Please test |
Hmm, something is wrong with OSX test run on CI, can we try again? |
Ah, yeah, we saw this yesterday on another PR. Strange. @swift-ci Please test macOS |
Build failed |
Okay, now there's a problem on master. Holding off for a bit longer… |
@swift-ci Please test |
Merged into master. Thank you, Aleksey! |
This pull request adds real
AccessScope
type that wrapsDeclContext
along with the bit, saying whether a top-levelDeclContext
should be treated asprivate
rather thanfileprivate
. This changes fixes the issue when diagnostics occasionally claims that declaration isfileprivate
even though the user has writtenprivate
.Now diagnostics claims correct messages, but there is one moment that I'd like to clarify.
Currently this is a valid code:
And we have tests for error message and fix-it:
I find this error message a bit confusing, it would be clearer to see this error message next to the protocol declaration:
Is this a valid point and should I start an evolution thread for this change?
Resolves SR-2209.