-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Analyzer needs a compatibility mode to support non-NNBD-aware clients #40500
Comments
…Sdk." This reverts commit 545b80e. Reason for revert: this is a breaking change for MockSdk users, actually much more serious issue for un-forking SDK. #40500 Original change's description: > Analyze SDK with non-nullable experiment enabled, update MockSdk. > > Change-Id: I80264a6533045c33ed794a5938f6719f3b5a6d0b > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/134401 > Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> > Commit-Queue: Konstantin Shcheglov <scheglov@google.com> TBR=paulberry@google.com,scheglov@google.com,brianwilkerson@google.com Change-Id: If8c3d8322986b01012fa76f9c928f35309c00e3e No-Presubmit: true No-Tree-Checks: true No-Try: true Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/134802 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Here's another possible implementation approach (and my current favorite).
The steps above are sufficient to allow us to safely unfork the SDK. The steps below are about getting proper functionality once NNBD is enabled:
@scheglov @bwilkerson @MichaelRFairhurst @jcollins-g what do you think? |
|
I'm open to the static variable approach (though I think we actually need to use a zone-local variable--see my further comments below).
You're right, my proposal would introduce some technical debt.
Something I really like about this idea is that it addresses the issue of what to do about
The analyzer API used by code generators is asynchronous, so it would be a little more complex than this. But I think it would still be doable if we use zone-local storage rather than a static variable, e.g.: static final zoneKeyForNullSafetyTypes = Object();
R enableNullSafetyTypes<R>(R body()) => runZoned<R>(
body, {zoneKeyForNullSafetyTypes: true});
class TypeSystem {
static bool _usingNullSafetyTypes => Zone.current[zoneKeyForNullSafetyTypes] ?? false;
...
}
I don't think we can immediately remove the flag and publish the breaking version, because we want to keep rolling the latest version of the analyzer into our internal codebase without causing any breakages. So we'll have to keep the flag around until all internally-used analyzer clients have been made sufficiently NNBD-aware that they won't be broken by hardcoding it to true. Also, since the purpose of all this machinery is to make sure that we don't have breakages when the SDK is unforked, it would be nice to wait until the unfork happens before ripping it out again, just to make sure there aren't any unanticipated breakages. But I think that's ok. Having the flag around is technical debt, but it's technical debt that we should be able to resolve before we publish full NNBD support, and I think it's lower impact technical debt than what I'd proposed. |
DartType.== implementations ignore nullability if the flag is not true. TypeSystem APIs eliminate nullability from types before doing anything. Internally analyzer continues using actual nullability for types. Package nnbd_migration opted into NullSafetyUnderstandingFlag. Bug: #40500 Change-Id: Ifeea28c01adf1dc59ed2da675b4a62c6334d529a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/134865 Reviewed-by: Paul Berry <paulberry@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
@scheglov should we close this issue or is there additional work to be done? |
We don't know of any additional work to be done. We may encounter more issues when we actually do the unfork, and if that happens we'll address them as they arise. |
Recently we updated the analyzer's mock SDK to be NNBD-enabled (545b80e). This turned out to break clients that use the mock SDK for their unit tests, but don't yet properly know how to handle NNBD types.
We'd thought that this would not be an issue, because NNBD types look just like legacy types, but with a new getter indicating their nullability. We'd thought that clients that don't yet properly know how to handle NNBD types would simply ignore that new getter. Unfortunately that didn't work, because those clients still make use of the analyzer's TypeSystem object to do things like subtype checking, and the TypeSystem object does understand the NNBD types. So there's a mismatch between the client's NNBD awareness and the TypeSystem object's NNBD awareness, and that's breaking invariants about subtype relationships that the clients rely on.
Fortunately, today, this is not a huge deal, because it is only affecting unit tests for analyzer clients, and we can always roll back the mock SDK change to un-break them. But when we un-fork the SDK, it's going to become a huge deal, because then it will affect code generators running in production.
As I see it, there are three broad approaches to solving this problem:
Prior to unforking the SDK, update all clients of the analyzer to properly handle NNBD types.
Add a "compatibility mode" to the analyzer allowing clients to opt out of seeing the consequences of NNBD types, and prior to unforking the SDK, update all clients of the analyzer to make use of this mode. Later, when a client has been modified to properly handle NNBD types, it can switch off compatibility mode.
Same as 2, but make compatibility mode the default behavior of the analyzer.
Option 1 doesn't seem feasible; it would delay unforking the SDK too much. Option 2 might be feasible, but it's scary; pushing this sort of change out through the ecosystem (even just in our internal codebase) sometimes takes weeks. Option 3 has the advantage that we can address it entirely inside the analyzer codebase, but the disadvantage that it forces compatibility mode to be a global switch, rather that something that the client opts into in a fine-grained manner.
However, we still haven't figured out exactly how to go about implementing this compatibility mode, and the way we solve that problem may make option 3 impossible. A few options we've discussed are:
When in compatibility mode, the analyzer could analyze all code as though it's opted out, but ignore errors like having an unexpected
?
after a type name. We'd also have to change the analyzer's summary resynthesizer so that in compatibility mode, it erases type nullabilities. Advantage: it's a global switch, so option 3 is possible. Disadvantage: suppressing all the errors may be difficult (for example, do we switch off flow analysis, and if so, do we have to suppress a lot of type errors?)The way compatibility mode could work is that every time a client gets an element (e.g. from an AST), they have to call
.toLegacyElement
. Advantage: easy to implement in the analyzer. Disadvantage: since it's not a global switch, so option 3 isn't feasible. Disadvantage: since the client must remember to call.toLegacyElement
whenever they access an element, there is a greater danger of forgetting to do so in a necessary place and not discovering until later, when it's not convenient to update the client (e.g. when trying to migrate a package that makes use of the client as a code generator).Implement compatibility mode using a boolean flag in the TypeSystem class; when in compatibility mode, it ignores nullabilities when making type comparisons. Advantage: it's a global switch, so option 3 is possible. Advantage: a relatively small amount of analyzer code is affected. Disadvantage: we would also need to make
DartType.operator==
ignore nullabilities when in compatibility mode, butDartType.operator==
doesn't have access to the TypeSystem object.Implement compatibility mode using a static boolean flag that affects the behavior of the TypeSystem class and
DartType.operator==
. Advantage: it's a global switch, so option 3 is possible. Advantage: a relatively small amount of analyzer code is affected. Disadvantage: injecting behaviors like this through a static bool seems like a poor design. Disadvantage: if two analyzer clients coexist in the same isolate, they might fight over the setting of the flag. (Note: I'm not sure whether this happens when using package:build, or whether package:build segregates code generators into separate isolates).The text was updated successfully, but these errors were encountered: