-
Notifications
You must be signed in to change notification settings - Fork 423
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
multiple-defined module symbol has shadowing instead of error #14014
Comments
The reason for this is due to the current interpretation of Bringing over a comment from #11262:
|
I think this code demonstrates non-intuitive behavior with how module symbols and module contents can confusingly shadow, but I attribute the behavior to I haven't thought of a way to create a conflict with |
It's interesting to me to note (if I'm not making any mistakes) that if (a) one were to always utilize Specifically: use M only;
use N only; // can only refer to top-level N due to `use M only;`
writeln(N.x); // can only refer to top-level N.x or 42 use M only;
use M.N only; // obviously refers to M.N
writeln(N.x); // can only refer to M.N.x or 100 because we haven't `use-d` submodule `N` use M only;
use N only;
use M.N only; // error: symbol `N` is multiply defined
writeln(N.x); That takes me more off the fence on the "must submodules be |
This example continues to haunt me... thanks for contributing it, Bryant. I continue to think it's reasonable to say "imports will handle this better", but it points to some problems with First, it haunts me because, though you didn't say it, it points to a potential example of hijacking through submodules. Let's say that my code had started out as a program that used two library modules: module M {
proc bar() {
writeln("Computing bar()");
}
}
module N {
proc foo() {
writeln("Computing foo()");
}
}
module Main {
proc main() {
use M;
use N;
foo();
bar();
}
} but then later, the implementer of module M {
proc bar() {
writeln("Computing bar()");
}
module N {
proc foo() {
writeln("Hijacked!");
}
}
}
module N {
proc foo() {
writeln("Computing foo()");
}
}
module Main {
proc main() {
use M;
use N;
foo(); // this now calls the hijacked version
bar();
}
} While I've rationalized above why This makes me wonder whether, more as Bryant's original intuition was saying, if a Also interesting to me is that there isn't any way to say I'm going to reopen this issue because of the unease it's causing me. |
👍
I think |
(Edited for outdated comment) My initial thought was to use the file name as the module prefix to reference the top level N, given that we insert that as a wrapper when there is other top level code. This doesn't work when only modules are defined at the top level of the file, but does suffice when there is a non-module symbol at the top level. E.g. this version of "foo.chpl" compiles: module M {
proc bar() {
writeln("Computing bar()");
}
module N {
proc baz() {
writeln("Hijacked!");
}
}
}
module N {
proc baz() {
writeln("Computing baz()");
}
}
module Main {
proc main() {
use M;
use foo.N; // prefix with file name
baz(); // this still calls the original
bar();
whee();
}
}
proc whee() {
writeln("in whee");
} But this does not: module M {
proc bar() {
writeln("Computing bar()");
}
module N {
proc baz() {
writeln("Hijacked!");
}
}
}
module N {
proc baz() {
writeln("Computing baz()");
}
}
module Main {
proc main() {
use M;
use foo.N; // prefix with filename
baz();
bar();
whee();
}
} Maybe we should consider always allowing reference to the file name for disambiguation in this way? |
I agree with the ambiguous module warning idea |
👍
I'm okay with this. It means that a dependency has changed in a meaningful way that would potentially break your code.[1] (I assume it's meaningful since the author made or left the inner module
My initial comments were intended as counterargument to the claim that symbols in Chapel are treated as equally as possible, since I don't think that's reasonable given that each symbol kind has a different design goal (functions can overload, variables can scope-shadow, modules shouldn't scope-shadow[1]).
Probably true, though at least to me, it's not apparent at all why "100" is printed even after learning the scoping rules. I'd be curious what most programmers would blind guess.
👍 👍 👍 #13915 [1] Once there's a way to specify a precise module you want to use, these conflicts can then only occur with the generic |
Another idea that @mppf mentioned on the phone last week (and credited to @BryantLam, though I don't think it's on this issue, and I may not have seen it (yet) in its original context) would be to always require fully-qualified module references in module M {
module N {
}
}
module N {
} The code: use M;
use N; would never get us to module use M [only [N]];
use M.N; or perhaps just: use M.N; This would obviate the need for a "root scope" qualifier for top-level modules at the cost of always needing to type more to I find the benefit here attractive (not that I'm opposed to introducing a root scope qualifier, but am not immediately drawn to any of the current proposals), but don't know whether the downsides would be considered attractive or unattractive to others (nor whether they're familiar / burdensome for those coming from other languages). |
It likely came from one of the many Rust links between @mppf and myself starting from #12923 (comment).
This is effectively how Rust 1.0 behaves. They've since introduced an explicit way to choose a module from some point in the hierarchy, which #13915 attempts to address. This is fine, but likely not what we should do. I would caution that absolute paths would break a lot of code in submodules, as submodules would no longer be able to directly As a consequence, this behavior also makes refactoring efforts via moving directories around also more painful, as not only will all ... Unless you meant only top-level |
@bradcray - I know we have made some progress in this area since most of this discussion (resolving #13915 for imports and deciding not to make |
@bradcray - I am trying to understand something about #14014 (comment) If we have this: module M {
proc bar() {
writeln("Computing bar()");
}
module N {
proc foo() {
writeln("Hijacked!");
}
}
}
module N {
proc foo() {
writeln("Computing foo()");
}
}
module Main {
proc main() {
use M;
use N;
foo(); // this now calls the hijacked version
bar();
}
} I am not following why I thought that perhaps it has to do with how the use-statement processing is breaking the tie between the top-level module GG {
module M {
proc bar() {
writeln("Computing bar()");
}
module N {
proc foo() {
writeln("Hijacked!");
}
}
}
module N {
proc foo() {
writeln("Computing foo()");
}
}
}
module Main {
proc main() {
use GG;
use M;
use N;
foo(); // this now calls the hijacked version
bar();
}
}
|
Oh, wait, it is at a distance of two as well, because one hop goes out of But that can't be what is going on because this variation on your program has the same behavior: module M {
proc bar() {
writeln("Computing bar()");
}
module N {
proc foo() {
writeln("Hijacked!");
}
}
}
module N {
proc foo() {
writeln("Computing foo()");
}
}
module Main {
use M;
use N;
proc main() {
foo(); // this now calls the hijacked version
bar();
}
} |
I've opened #19167 which asks about the design of shadowing more broadly and would impact the behavior of programs in this issue. |
I've opened #19219 about the two implicit/hidden/shadow scopes for |
I just wanted to bring up one thing from #14014 (comment)
I might be splitting hairs but I wouldn't think about it as making module symbols special and different from variables; rather I would think about it as defining how |
Following up with a conversation I had with @bradcray - I think that the two of us are in agreement that we can make I think the remaining question is - when the One argument for only considering modules/enums is that it keeps the error focused on cases that are actually problematic instead of making it an error that introduces noise - in particular if the "closest" definition of |
A further related question - would it be OK to resolve the ambiguity with say |
Thinking about this question first made me think about the following case (which I see now you also brought up in your third paragraph): module M {
}
module N {
module M {
}
}
module Main {
use N;
var M: int;
use M; // should refer to `var M: int;` and generate an error
} where I'd expect that we should get an error (e.g., "Cannot So then, if the normal shadowing scoping rules only find a single symbol and it's a module or enum, then we'd search further for other modules/enums. Or possibly "only if it's a module" and/or "only search for other modules," since enums probably aren't capable of hijacking in a way that wouldn't simply cause compilation errors(?). Or am I being naive about that? But I think that if we find multiple equally-visible symbols without doing the deep search, we'd need to generate an ambiguity warning rather than preferring the module for any reason. E.g., it seems like this case should report an ambiguity? module P {
module M {
}
}
module N {
var M: int;
}
module Main {
use P;
use N;
use M; // Ambiguous: `var M: int` and module `P.M` are equally close?
} And then here's another case that I think would remain an error (probably not up for debate, but as long as I'm playing with these): module M {
}
module N {
var M: int;
}
module Main {
use N;
use M; // Error because `var M: int;` is closer?
} Basically, the case I want to avoid is that
I think if this falls out through normal rules, that'd be fine (if not particularly clear), but I wouldn't do something special to make it do something heroic. For the example in #14014 (comment), if I have to change my code to resolve the ambiguity anyway, I'd just as soon change it to something like |
I also agree with Brad's position here.
I'm not sure I fully understand this. If we've already found a single symbol that works, why are we looking further? Or is it only in certain circumstances to do with the "merged" scopes?
I agree with this and the rest of Brad's comment |
Right, I was talking specifically about cases where a variable has the same name but wouldn't be the "closest" one that we pick. module HasVarM {
var M: int;
}
module Main {
module M { }
use HasVarM;
use M; // module M is closer, so we prefer that.
// Should it be an error anyway, since M is arguably ambiguous?
} |
This an approach Michael proposed for avoiding the silent hijacking type of situation brought up in: #14014 (comment) The proposal is to look harder for these types of ambiguities for |
Resolves Cray/chapel-private#5220 Resolves #19780 Resolves #19782 This PR introduces a warning for cases where symbol shadowing might be surprising. In particular, the warning is focused on cases where a `use` statement brings in a symbol that conflicts with a more obviously available symbol in an outer scope. This warning is motivated by the example in #14014 and the the hijacking example in #14014 (comment). Additionally, in review of PR #19306, it was requested to make a warning about the case that a `public use` symbol shadows a `private use` one and this PR implements that request. The new warning only applies to scope lookups for the innermost match; that means that the new warning does not apply to functions and calls. What is the logic for the warning? * when a symbol is available through a bulk import from a `use` statement, warn if it's defined in another available scope (another shadow scope or outer scope) except: * do not warn for locally defined symbols that shadow a symbol from a private use (shadow scope 1 or 2) * do not warn for conflicts between a name brought in by a private use and the module name brought in by the same private use The warning is implemented to consider all parent scopes and the names of toplevel modules, if we are considering a `use`/`import`. Considering the parent scopes seems natural to me but the toplevel module part is necessary to give the warning in the case of #14014 (comment) . We considered avoiding warning in the case that the toplevel module in question is in a different file but opted not to do that. Since the warning is (for the most part) a superset of the warning for hidden formals added in PR #21614, this PR removes the hidden formal warning. * The case where a function is found through a `use` statement but has the same name as a formal is no longer warned about because the new warning does not apply to function lookups (test/functions/iterators/recursive/recursive-iter-findfilesfailure) * The case of a local variable that shadows something from a `use` statement that in turn shadows a formal is no longer warned about, by the design of the new warning (test/modules/diten/testIfModuleShadowsLocal) Future Work: * consider a similar error for function calls Reviewed by @DanilaFe and @lydia-duncan - thanks! - [x] full comm=none testing
Forked from #11262 (comment)
This code prints "42" regardless of ordering for the two
use
statements in block scope. I think it should be an error sinceN
is multiple-defined.chpl version 1.20.0 pre-release (8cb22586)
Note that in
chpl version 1.19.0
, this code always prints "100" instead. [TIO]Edit: From Brad's explanation in the next post, this code is supposed to always print "100".
The text was updated successfully, but these errors were encountered: