-
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
Add compiler warning when use
statement is not at start of scope
#13041
Comments
To me, this seems more related to the plan to support a Python-style Maybe put another way, I think it's often useful to put a |
Agreed.
This is where I disagree with the rationale for why module A {
var keepWorking = false;
}
var keepWorking = true;
proc main() {
while keepWorking {
writeln("critical operation!");
var someCondition = true;
if someCondition {
break;
}
}
use A only keepWorking; // Remark: Why, user?! Why? Such bad code...
if keepWorking {
writeln("operation just for A");
}
}
// Hint: Nothing gets executed. The proposed warning would force the user to introduce an indented scope if they want a |
Ironically, I hit a problem when abiding by this guideline. The following code broke with a nested scope: var e: [1..8] int = 1..8;
{
use UnorderedCopy;
// UnorderedCopy or its dependencies has a symbol conflict on `e`.
ref eArray = e; // error: Cannot set a reference to a param variable.
writeln(eArray);
} This error took a bit for me to debug because the compiler error didn't have a note on where the param variable was. This error is easily remedied:
|
I agree that this example is bad news / super confusing. This is a pretty interesting case because I think the fix here is to implement issue #6093 (adding support for private, non-transitive |
I've forked off issue #13118 to capture the case Bryant ran into just above. |
Just noting for anyone who didn't subscribe to my forked-off #13118 that the issue Bryant ran into above is now resolved on master due to @lydia-duncan 's PRs #13372 and #13253 (as well as other supporting PRs). |
Somehow proc f() {
innerFn();
proc innerFn() { }
} seems fundamentally less confusing to me than proc f() {
someFn();
use M; // assuming M defines someFn
} It looks like a trade-off between reducing one arguably surprising use of modules and making them in some way more consistent with locally-defined symbols. Which would be more useful to easy understanding of the language itself and code written in it? Personally, I think the Anyway, the rule can't possibly be "use has to be at start of scope" because interpreted literally that would disallow proc f() {
use A;
use B; // not at start of scope
...
} I think instead the proposal I would advocate is that the compiler emit a warning if code relies on a symbol brought it by a later |
I'm okay with both proposed solutions. I would advocate for the second. This issue was a consequence from the confusing behavior I found in #12744. Maybe there's more support for changing this behavior now that module symbols are treated a bit uniquely as of 1.20. |
I am wondering if we are still thinking of changing this? Some of the proposed changes would be breaking language changes, even if they adjust a pattern that is confusing and probably not occurring very frequently for that reason. |
I continue not to be much of a fan of this proposal, at least from the perspective of having it be on by default. I wouldn't object to an "opt-in" feature for it.
Do you mean "breaking" in the sense of "your code will generate warnings where it didn't before?" or in the sense of "your code won't work anymore"? If the latter, what's an example? I think I missed that the first time around. |
In #13041 (comment) @BryantLam was advocating for "not allow the code before the use statement to have access to the symbols it brought in at all." which would not be a warning but rather a fatal error. |
Ah OK, I was focusing too much on the OP's request when interpreting "changing this" in your comment. I personally don't have much appetite for doing more than a warning here because it feels like a big change (placement of I'd be less opposed to:
|
When I first was looking at this issue I was just wondering if we could close it but now I'm thinking we actually need to make the breaking change.
But doesn't the order of use statements already matter? // Example A
module Library {
module SubModule {
var str = "str from submodule";
}
}
module Program {
// this does not currently compile, but swap these two statements and it does
use SubModule;
use Library;
proc main() {
writeln("str=", str);
}
} I care about this because I have been hoping for the reworked compiler to combine scope resolve and resolution into a single "pass" that generally operates in program order (departing from that to go resolve a called function etc). I understand that we'd like to be able to refer to a class, module, or function declared later in a scope. But I think my mental model of the production compiler has been that it handles non-function identifiers in one pass (scopeResolve) and then function later on. But here I think the behavior is implying that there are currently actually 3 passes:
Now, why would I care about combining these into one "pass"? There are features that are quite hard to build in the current construction. One is having a class inheriting from an instantiated generic (e.g. However there is a similar functionality that seems related to this ordering issue. Say we wanted to support something along the lines of #17438. Here's an example: // Example B
param favoriteModule = "Math";
proc computeRequiredModule() param {
... uses of other identifier possibly from a use stmt ...
return favoriteModule;
}
use computeRequiredModule(); This example seems intuitive and straightforward, but if we process the Here is another related example that shows that // Example C
module Helper {
param favoriteModule = "Math";
}
module OtherModule {
param favoritePrefix = "";
}
module Program {
use Helper;
use computeRequiredModule();
use OtherModule;
proc computeRequiredModule() param {
return favoritePrefix + // favoritePrefix comes from a use after the one calling this fn
favoriteModule; // favoriteModule comes from a use before the one calling this fn
}
} Now, this can't possibly compile, because we have to process the use statements in order (see Example A). But if we have the language design approach that function bodies etc are all resolved assuming that all Other variants have a similar problem. This version uses a // Example D
config param moduleName = "bla";
use moduleName; This example is reasonable to handle with in idea of "resolve the use statements and dependencies in order" but if Here is the example from #17438 (comment) : // Example E
use Version;
use (if chplVersion >= createVersion(1, 24) then Memory.Diagnostics else Memory); How can we resolve / evaluate the conditional if all use statements can provide identifiers for it? There's a circularity problem. At the very least the contents of the expression in the use statement would need to be resolved in order of the use statements. What I'm trying to show in the other cases here is the way that the current "resolve use statements in order" rule could extend naturally to resolving other things - or it could conflict with the idea that other things are always resolved assuming the use statements are processed. |
Yeah, if we were to enable use and import to get modules by resolving more complicated syntax, we'd definitely need some sort of tracking of which use/import statements rely on which symbols that rely on which other use/import statements to determine what code combinations are and are not valid. Allowing use/import statements to occur at any point in a scope complicates that, but so does allowing functions to be called from any point in a scope. I think in practice there aren't very many codes that rely on use/import statements behaving this way. However, I'm cautious about deciding to do away with it as a result - the concept of keeping a strict order for code is not new and so was definitely considered when we originally decided use statements should be possible at any point in the scope and affect symbols defined before it lexically. It's important to me to understand the reasons that caused us to make this decision, even if we decide to move away from it. |
That's a good point. If I were arguing for the status quo, I might say that relative order between I need to stew on the rest of your proposal. The concept of supporting conditional |
Not exactly, but I was thinking about how the ordering requirements on In a conversation with @lydia-duncan I learned that the production compiler works by resolving the use/import statements first and then proceeding on to the other declarations (in scope resolve, so we're not talking about functions yet). I think this implementation detail supports your conclusion that the order between However I think that the use statements being processed in order (and only affecting things below them) makes more intuitive sense than the alternative that we have today. Obviously that's subjective. I'd even go so far as to say that, with the current rules, relying on the ability to resolve a symbol brought in by a later
I'm pretty sure it could be done (at least, with the ordering change we are discussing) in a way that's reasonable and unified with the rest of the language. Whether it should be done is question that needs more investigation. I'm not particularly tied to the use-a-param feature; I was just trying to show some potential future directions and problems we are likely to run in to. I suppose some other languages have "first class modules" and I'd argue that any features along those lines will run into the same problems. (And there are almost certainly other ways to solve these problems beyond my proposal - but it's hard to say without talking about a specific problem.) |
So, thinking about this en route to dinner I found myself wondering whether you were also going to propose as part of this change that a use-before-def of a variable within a given scope be changed to attempt to refer to an outer-scope variable of the same name rather than the local scope variable of that name, as in languages like C: var x: int;
{
writeln(x); // currently refers to the `real` version and results in an error; you might be about to propose it refers to the `int` version?
var x: real;
} If not, how does the single-pass approach work? (a possible answer: variable resolution is done at the same time as function resolution, which would also unify paren-less functions and variables better). But I'm not entirely clear how the single-pass approach works for functions either... For example, if I have: proc foo(x: int) { ... }
{
foo(1.2);
use Foo; // introduces a `foo(x: real)` overload
} is the implication that Last night, trying to argue your side, I was going to suggest that since
I suppose I agree that they're more complicated than the (typical) variable, procedure, or class definition in that they're making pre-existing symbols available rather than creating new ones, but at the end of the day, like |
Nope, I'm happy with the current behavior here.
Actually "single pass" is a bit of an approximation. I'm imagining that the compiler can gather the declarations immediately in a scope (so when processing the block in this example, it will gather the
In this example, I'd expect that the (But it's also not quite accurate to say that all function resolution would occur in order - things like generics still need to be resolved according to when they are called. As we are talking about having more order constraints on
Two things:
Vs. for a function or variable declaration, it has a name which is right there and immediately obvious in the syntax. |
So, if we were to go that route, then I think we should definitely either:
The reason is that it seems unintuitive to me that a var x: int;
{
writeln(x);
use Foo; // defines `var x: real;`
} would act differently than the previous version of the example that defined In saying this, I'm not saying that my preference is to go this route rather than what we have today, simply to say that if we were to re-interpret
I'd argue that |
Feature request for a compiler warning. Low priority until someone else runs into this behavior.
Create a warning for #12744 when a
use
statement is not at the start of scope. Otherwise, bugs will occur when users encounter this behavior.The text was updated successfully, but these errors were encountered: