Comments (20)
each conditional library will be analyzed independently as well,
That can fail if the libraries and class have dependency cycles.
If, say, a library defines a factory class, conditionally imports and exports an implementation class, and the implementation class refers back to the factory class, then at most over of the implementations will see itself as the type returned by the factory class.
The rest will have your errors.
With parts, it's just more likely that there are cyclic dependencies, even if each part could be analyzed independently.
Would it be worth defining some configuration that a part assumes,
Like
part of "parent.dart" if (dart.library.io);
I guess it makes no difference, the condition can be trivially inferred from the part
declaration that refers back to the part file. It's the ability to hold multiple versions of the same library that's needed.
from language.
each conditional library will be analyzed independently as well,
That can fail if the libraries and class have dependency cycles.
...
With parts, it's just more likely that there are cyclic dependencies, even if each part could be analyzed independently.
I think that cyclic dependencies can cause analysis to fail in the sense that the analyzer could produce false positives. But if we don't do something more for parts we could have false negatives (by not analyzing whole subtrees).
I think users will be more (negatively) impacted by not seeing errors in their code until they build for a given platform than by seeing false positives. That doesn't mean that I'm convinced that we have to analyze every part, I just wanted to make sure everyone is aware of the situation and that we've made a deliberate decision.
It's the ability to hold multiple versions of the same library that's needed.
Do you mean "needed" in the sense of "what would be required to completely solve the problem" or in the sense of "is a requirement we're placing on the analyzer"?
from language.
FYI, in Rust code that is excluded by #[cfg()]
is not analyzed semantically.
It is parsed, and parse errors are reported.
But no resolution.
Note that on this screenshot windows
code is greyed out.
![image](https://private-user-images.githubusercontent.com/384794/347576810-1c222d1e-4b67-41da-9ecb-c00ec204b7ca.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjExNTU3OTcsIm5iZiI6MTcyMTE1NTQ5NywicGF0aCI6Ii8zODQ3OTQvMzQ3NTc2ODEwLTFjMjIyZDFlLTRiNjctNDFkYS05ZWNiLWMwMGVjMjA0YjdjYS5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwNzE2JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDcxNlQxODQ0NTdaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0yOTBjNDMyODAyNzgxNzZjOWQ4YzUwNWEyMWMyMDEzZWUzMDY5ODlhM2Q4ZTVhM2YyOThlYjUxODAxNDJjZmU3JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.EN5IG5AF0WYupcpXSwbUlMZFAjhWDkvam_AvxYOdaLI)
from language.
How does rust manage setting up the configuration such that this can happen? IIRC the main issue the analyzer has had is that it doesn't have any way of telling it what configuration to analyze with? Can we learn something from them?
from language.
tl;rd there are separate ways / levels for rustc
, cargo
, and IDE.
Documentation is here.
Some of configuration options are compiler-set, such as target_os
.
Other can by declared by the user.
I think we can provide them with --cfg
to the rustc
- the Rust compiler.
I don't know if this is the right level, my current understanding is that going through crate
is preferred.
For crate
, to run with crate run
(which will invoke rustc
for us), we can work with features.
If I run with cargo run
, nothing is printed.
If I run with cargo run --features print_enabled
, it will print.
Or if I enable the feature by default with default = ["print_enabled"]
in Cargo.yaml
.
Also, notice that there is the gray mark in RustRover.
If I toggle it, this changes whether RustRover compiles the configured code.
I was not able to find where it keep this flag, maybe somewhere outside the project directory.
This does not affect what cargo
does, only analysis in the IDE.
And because this is the same flag, it enables all pieces of code that use it.
from language.
Currently we analyze with default URIs, which works to some extent.
But we don't analyze and report any diagnostics for non-default configurations.
For example a non-default imported library could miss one of the invoked functions.
And the user will know about this only from the compiler.
I can see several possible solutions.
- Say that this is fine.
- Support a way to provide configuration flags in
analysis_options.yaml
or similar file. - Like (2), but let the user to describe the set of values for each flag, and the analyzer should analyze each affected file in all combinations.
For conditional parts the situation is wore because a part cannot be analyzer outside of its library (in contrast to a conditionally imported library). So, we will not able to analyze these parts at all - no diagnostics, no semantics functions like navigation or code completion. So, (1) is not a good answer anymore.
I suspect that (3) theoretically could be implemented, but would be fairly complicated.
from language.
(3) would also be expensive right? Would it be a whole new analysis context for each, which would separately analyze the whole project?
from language.
Actually, you are right, we could do (3) in a much less complicated way, just for separate analysis contexts.
Computationally it will be as expensive as necessary to reflect the potential effect of each flag.
If some libraries don't have anything that depends on a flag in their transitive dependencies, we will reuse from summaries their element model, and diagnostics. There will be some CPU / heap cost of creating FileState
models and computing hash based cache keys.
And if something is affected, there is no choice - we have to compute new results.
from language.
(3) would also be expensive right?
Yes. It would be expensive in terms of performance because we'd have to repeat the analysis once for every combination of flags that we needed to analyze against. And it would be expensive in terms of memory for the same reason, though we might be able to share some flag-independent state across contexts.
And if the exported namespace of the library is different under different combinations of flags, then we'd theoretically need to analyze every library that imports it against each version of the namespace.
And then, of course, there's the added complexity for the user: such as needing to figure out which combination of flags caused a given diagnostic to be reported.
The question is: which situation would be best for users, and can we afford to implement it.
I do have to ask, though, whether there might be a fourth option. I think we could analyze each of the only-conditionally-included parts against the default namespace of the library (and for conformance with the default part), and not add any declared names to the default namespace. That wouldn't be perfect. There'd be both false positives and false negatives in some situations, but it might be less expensive than a complete analysis while providing better feedback for the user most of the time.
from language.
We can do any computation infinitely faster, if the correctness of the result does not matter :-)
I think in (4) it would be annoying to have either false positive, or false negative diagnostics in non-default conditional parts. And similarly, it would be inconvenient to be not able to use correct code completion, navigation, and other semantic services.
Just give me full fidelity in one combination, i.e. (2). Want to focus on Windows (or dart:io
, whatever) portion - say so; then switch to another configuration - validate it, make changes, etc. Not all configurations at once, but all features in one (non necessary default) configuration.
I agree about complexity of (3) for the user. We could try to mitigate this by adding the combinations of flags that produced a diagnostic, when not every combination produces them.
(3) is definitely not free, and will use more CPU and heap. But it might be up to the user to decide what he wants - manually iterate over combinations of flags, or pay the price and get it all automatically.
Although... even if we display a diagnostic, e.g. the type of the argument is not assignable to the type of the formal parameter, if the navigation does not go to the location of the invoked method, it would be not convenient.
from language.
I can't say with any certainty how often there would be false positives or negatives in (4). So while I agree that false positives and negatives would be annoying, I don't know whether that would be a problem in practice.
I have two concerns with (2). The first is discoverability. I don't think it would be obvious to most users that they can set the combination of flags in a file and have it control analysis. The second is usability. Even knowing that the control exists, I have to wonder whether users would remember to change the configuration when they need to do so. We might need to prompt the user, for example by displaying a message saying something like "You are editing a file that is not currently being analyzed because of the configuration selected in the file. In order for this file to be analyzed you need to set to true.". But that too could become annoying.
Perhaps we need to get some feedback from users via a survey question or a user study?
from language.
I think it is trivial to construct a false positive case for (4).
part 'foo.dart'
if (dart.library.html) 'foo_html.dart';
void f() {
do_foo();
}
In foo.dart
void do_foo() {}
In foo_html.dart
class FooHtml {
void do_foo() {}
}
void do_foo() {
FooHtml().do_foo();
}
If we don't analyze foo_html.dart
with the right scope, it will not able to resolve FooHtml
(because if the part is not included into the library, its top-level declarations are not included), and report a diagnostic.
And the false negative is this structure, but when FooHtml
is removed, and we don't report diagnostics for non-default URI parts to avoid the previous false positive.
Using a header like we have for pubspec.yaml
files in IntelliJ might be a good way to solve both issues: the user will learn that the file is not analyzed, and it can have a reference to the documentation that describes how to set values for flags.
from language.
If we don't analyze
foo_html.dart
with the right scope, it will not able to resolveFooHtml
(because if the part is not included into the library, its top-level declarations are not included), and report a diagnostic.
I agree that using the right scope during resolution is critical. I guess I just assumed that the scope we'd use for a given part would include the declarations in that part (and any other part in the subtree rooted at that part).
More generally, I have to wonder whether we couldn't create a scope for the analysis of these conditional parts that would minimize both false positives and false negatives.
from language.
Now imagine that FooHtml
is exported from bar.dart
export 'bar.dart'
if (dart.library.html) 'bar_html.dart';
and imported into the library:
import 'bar.dart';
part 'foo.dart'
if (dart.library.html) 'foo_html.dart';
void f() {
do_foo();
}
and we still have foo_html.dart
void do_foo() {
FooHtml().do_foo();
}
If we did not use the same dart.library.html
flag during linking bar.dart
, we will get false positive.
from language.
True. I already conceded that (4) would have both false positives and false negatives. The question is whether the number of times we erroneously analyze code is small enough in practice to make this a viable alternative.
I am still concerned by the implications of (2) as mentioned above, as well as a third issue I've thought of, which is the danger that someone on a team might accidentally check in the file that specifies the configuration so that other team members' analysis might change without them realizing it. The idea of requiring users to be aware of the current configuration seems problematic to me.
If
- users could specify possible configurations in a file,
- we could display the current configuration in an easy to see place, and
- user's could dynamically change the current configuration by selecting it in a drop-down
then I'd be more comfortable with it because it would be more visible and easier to use. But I don't think any of those are actual options.
from language.
I think this is a bad exchange: correctness vs. awareness.
If the developer knows what he is doing, with (2) he can always get correct analysis.
With (4), if there is a false positive, he is out of luck, no solution.
The current active configuration is not any different than any other configuration in analysis_options.yaml
(what if someone ignored a lint?), or anything in Dart code. This is why there is code review.
Why don't we display easily all active lints in our IDEs? :-)
There is a precedent in Rust, there are many crates that have features that can be optionally enabled. So far I have not seen any warnings in books that I read that this is bad and should be avoided. And in RustRover code that is behind a not enabled condition is not analyzed semantically. Developers use Crate.toml
to configure enabled features just fine.
from language.
I think this is a bad exchange: correctness vs. awareness.
In general, I agree.
The question in my mind is the percentage of users that will see an incorrect analysis in each case.
For (4), all users working on non-idiomatic code will see incorrect analysis. But if the number of users working on non-idiomatic code is small, then so will be the number of users impacted by this choice.
For (2), any user that doesn't understand how our tool deals with configurations or how to change them will see incorrect analysis. Users don't read documentation, and the ability to set a configuration would be new and hard to discover, so I'm concerned that most users won't understand it. The number of impacted users would be limited to those working in code bases with conditional parts, but I don't have a good guess as to how many that will be.
Also, the failure mode for (4) is generally going to be obvious. The failure mode for (2) is that there will be that there's no indication that some of the code wasn't analyzed.
The current active configuration is not any different than any other configuration in
analysis_options.yaml
...
Yes, it is. The active configuration determines which code is analyzed at the moment; the other configuration options (other that the exclude
list) configures how the code will be analyzed, but not whether it will be analyzed. (And the exclude
list is a 'never' not a 'sometimes yes sometimes no' kind of control, which in my mind is fundamentally different.)
This is why there is code review.
In a code review it isn't going to be obvious which configurations the author had active while they were developing the PR. So unlike today, a review won't be able to trust that the author would have seen any valid diagnostics in the code (if it's conditionally included). That sounds like a really bad idea.
But I'm glad you brought this up because it reminded me that we also need to consider the behavior in a build environment. I don't like any of the existing options in that environment. I don't think users will be happy to have build systems that don't do any analysis except under one configuration.
There is a precedent in Rust ...
That's good to know, but it doesn't mean that Rust is doing a good job or that we shouldn't try to do better.
from language.
Yes, on CI the package must be tested with all combinations of configurations.
Often this means running in different runtime environment - different CPU, OS, etc.
As I see this, we could allow defining a sets of configurations, again maybe in analysis_options.yaml
; and the dart analyze
should be able to analyze the package with every configuration, one by one. But in IDE you can have only one set enabled.
You might think that "the users" of this feature is a wide auditory. I suspect that there will be a small number of power users who write multi-platform packages, and they might be capable to keep in mind that if they write such code, they need to validate it on all platforms.
from language.
I suspect that there will be a small number of power users ... capable to keep in mind ...
That's likely to be true. And maybe the right answer is option (1) because there are too few users for us to do more.
from language.
"what would be required to completely solve the problem"
It's what I mean. It would be nice, but it's not a requirement.
The Dart language is specified so that it gives a single meaning to a single, stand-alone Dart program and a single provided compilation environment.
Anything else is not the language, it's just SDK tooling.
from language.
Related Issues (20)
- Support paired-up private and public variables: "Private var, public final" HOT 3
- Let a wildcard as an actual type argument indicate a request for inference HOT 2
- Partial Record destructuring HOT 4
- Is future returned immediatelly from async call? Inconsistency in docs HOT 3
- Allow augmenting member declarations immediately in the same body? HOT 7
- [parts-with-imports] Consider dropping the library name from the library directive HOT 1
- Infer the type of an optional parameter from the default value
- Should digit separators be supported in int.fromEnvironment? HOT 1
- Support for Rust like #[cfg()] attributes HOT 10
- Allow all variable augmentations to omit the initializing expression HOT 4
- `Error: Method not found` when running Flutter app with macro class HOT 2
- Allow a variable initializer to be unresolved, following the treatment of abstract members HOT 3
- Allow Iterators To Use The for-in Syntax HOT 5
- Macros: Emit diagnostics inside macro arguments HOT 2
- URI shorthands without internal whitespace.
- URI shorthands, allow reserved words.
- URI shorthands, more permissive HOT 1
- Inheriting a type parameter bound?
- Ordering of augmenting initializers and getters on late variables
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from language.