dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.04k stars 1.55k forks source link

Compiler should not print warnings #46264

Open Hixie opened 3 years ago

Hixie commented 3 years ago

When I compile this:

void main() {
  const bool test = true;
  print(test!); // ignore: unnecessary_non_null_assertion                                                                                         
}

...I get:

test.dart:3:9: Warning: Operand of null-aware operation '!' has type 'bool' which excludes null.
  print(test!); // ignore: unnecessary_non_null_assertion
    ^
true

I'm happy for the analyzer to give the warning, but why is the compiler also doing so? And how do I silence it?

The context is that I have code (a Flutter plugin) that needs to compile against two versions of an API. In one version (Flutter stable) a particular field is nullable, in the more recent version (Flutter master), it's non-nullable. I don't mind having lots of // ignores in the code during the transition, but I don't want users to be faced with lots of warnings they can't do anything about when compiling their apps.

lrhn commented 3 years ago

This warning is a language specification mandated warning, which is why all source tools implement it. Not all source tools understand ignore comments, though.

We may want to make warnings ignorable in other tools too, or make other tools not print even spec mandated warnings (let the analyzer be the only tool which does warnings).

Hixie commented 3 years ago

Yeah, IMHO we should change the spec here. From a developer experience perspective, the analyzer is where the warnings are useful and the compiler should just always be silent unless it really can't build the code, IMHO. It just makes sense from a developer's point of view to have one source of places for messages about how the code is wrong.

If for some reason we really need the compiler to show warnings, having it implement // ignore would be ok too I guess.

jcollins-g commented 3 years ago

From a user perspective, we should consider that there are three, rather than two, cases where language warnings are relevant.

The first is when a user analyzes code statically via the analyzer. The second is when a user compiles an application to an executable format without running it. The third is when the user is running an application in such a way that it has the side effect of compiling it. I think this is the case @Hixie is talking about?

It is fine IMO to emit warnings in the first and second cases, but the third case should not emit non-fatal warnings, ever. This is because the user or other programs intended to interpret the stdout/stderr of the code, may not expect there to be warnings in the output because after all, the program didn't print them itself and ran just fine.

jcollins-macbookpro2:~ jcollins$ cat > foo.dart
void main() {
  const bool test = true;
  print(test!); // ignore: unnecessary_non_null_assertion                                                                                         
}
jcollins-macbookpro2:~ jcollins$ dart foo.dart
foo.dart:3:9: Warning: Operand of null-aware operation '!' has type 'bool' which excludes null.
  print(test!); // ignore: unnecessary_non_null_assertion                                                                                         
        ^
true
jcollins-macbookpro2:~ jcollins$ dart foo.dart 2>/dev/null
true
devoncarew commented 3 years ago

I'm triaging this to dart-dart-cli as I believe that the resolution - if that's what we agree on - would involve changes to dart run (both the implicit version and the explicit version). That is, the verbosity setting for compiling should default to 'all', while the verbosity setting for running should instead default to 'error'.

    --verbosity                                        Sets the verbosity level of the compilation.

          [all] (default)                              Show all messages
          [error]                                      Show only error messages
          [info]                                       Show error, warning, and info messages
          [warning]                                    Show only error and warning messages

cc @bkonyi

Hixie commented 3 years ago

I don't really see why #2 should print warnings either. It doesn't print lints, and some of the "warnings" we print (e.g. that ! was used redundantly) are much less serious than some of the lints we don't print (e.g. no_logic_in_create_state).

When you're running flutter build (compiling), you're past the point where being told about a stray ! is really relevant. You've clearly decided that that's the way it's going to be, and you just want an APK or whatever.

That said, if there is a way to mute the compiler in general then I would be satisfied with just having the flutter tool always mute the compiler; I have less investment in what the dart tool does. (cc @jonahwilliams)

jonahwilliams commented 3 years ago

the flutter tool talks to the compiler over stdin/stdout, and what we get over stderr is completely unstructured. Its not possible today for the tool to tell the difference between a warning and an error, so we'd either need to show all of it or none of it.

This came up in the past during the FFI stabilization, the warnings were ultimately removed IIRC.

Hixie commented 3 years ago

Any chance we can prioritize this? It's currently blocking one of the PRs I'm trying to merge over in Flutter land.

cc @leafpetersen

natebosch commented 3 years ago

Some prior discussion about case 3 (compiling code in order to immediately run it) in https://github.com/dart-lang/sdk/issues/34137

renatoathaydes commented 2 years ago

I am seeing this warning as well in a very unexpected place.

I have a package, actors, which is now emitting this warning when a binary depending on it is compiled:

▶ dart compile exe bin/dartle.dart
Info: Compiling with sound null safety
../../../.pub-cache/hosted/pub.dartlang.org/actors-0.8.1/lib/src/isolate/isolate_actor.dart:35:39: Warning: Operand of null-aware operation '??' has type 'String' which excludes null.
  final isolateName = Isolate.current.debugName ?? '';
                                      ^
Generated: /Users/renato/programming/projects/dartle/bin/dartle.exe

This is very weird because I am using Dart 2.15.1 and the actors package uses the environment config sdk: '>=2.15.0 <3.0.0' (same as the package I am currently compiling), which I updated to try to get rid of this warning.

Isolate.current.debugName is certainly nullable, and it appears to have been nullable at least since Dart 2.9.0: https://api.dart.dev/stable/2.9.0/dart-isolate/Isolate/debugName.html (it wasn't nullable up until 2.8.0).

What could be the issue?

EDIT: created a new issue as I think this one is not really very related.

jacob314 commented 2 years ago

+1 on changing the spec so that compilers only emit errors leaving all warnings to the analyzer. I'm not seeing any value from showing warnings in the compilers as well as the analyzer. Showing warnings from the analyzer works better as we know which packages are in the user's workspace so only show warnings that are actionable for the user to address. Warnings from dependencies are not actionable and we are costing package authors time to try to avoid them on all possible versions of Dart&Flutter that their package might be used with.

An alternate solution would be to teach the compilers about the user's workspace (current package for the simple case, multiple packages for the monorepo case) but that seems like a lot of extra plumbing for minimal benefit given users already see the correct list of warnings in the analyzer.

leafpetersen commented 2 years ago

I'm not seeing any value from showing warnings in the compilers as well as the analyzer.

As a counterpoint to this, just about every other language I've looked into does provide errors and warnings via the compiler (the notion of a separate analyzer is somewhat idiosyncratic to Dart), so I do wonder whether leaning into the way that Dart does this is the right solution. This is especially true if we want to move towards a world where more of the code is shared between the two. This proposal seems to me to be the most consistent and forward compatible.

Hixie commented 2 years ago

Dart is IMHO superior to other languages precisely because of this difference. Execution time is a much worse time to give warnings and errors than during development.

leafpetersen commented 2 years ago

Execution time is a much worse time to give warnings and errors than during development.

Agreed, but Dart is now also an AOT language, in which compilation time and execution time are not always the same. This is why I liked the specific proposal I linked to above: it separates the notion of what tool provides the errors and warnings from when the errors and warnings are provided. If you are developing with the compiler, you should (at least have the option to) get errors and warnings. If you are executing with the compiler/JIT, you should only get errors.

In general, I'm resistant to us building in deep assumptions about how people use our tooling stack without good reason. I see no reason not to support the ability to run a single command to get errors/warnings/compiled output, the way that just about every other tooling stack I've every used supports.

Hixie commented 2 years ago

I wouldn't object to having a way to get the compiler to also output all the things the analyzer tells us, sure. What I object to is having a different concept of "non-fatal message" from the compiler than what the analyzer outputs. For example, having different lints trigger warnings in the compiler than in the analyzer, having // ignore be ignored by the compiler but not the analyzer, etc. Right now we seem to have two different worlds. IMHO that is a very confusing experience, it's like our tools are having an argument with each other about what the language is.

leafpetersen commented 2 years ago

I wouldn't object to having a way to get the compiler to also output all the things the analyzer tells us, sure. What I object to is having a different concept of "non-fatal message" from the compiler than what the analyzer outputs. For example, having different lints trigger warnings in the compiler than in the analyzer, having // ignore be ignored by the compiler but not the analyzer, etc. Right now we seem to have two different worlds. IMHO that is a very confusing experience, it's like our tools are having an argument with each other about what the language is.

Yes, I very much support unifying the experience. That said, there are (at least now) some ways in which the compiler is the only way to get some errors and warnings, since the analyzer does not (I believe) support analyzing with different settings for conditional imports.

jacob314 commented 2 years ago

To be clear, I fully support the compilers emitting errors. My concern is with the compiler emitting non-errors, particularly non-errors that are outside of the scope of the user's workspace like it does now. What we are doing now is similar to if a Java compiler emitted warnings from analyzing .jar files you imported. As everything in Dart is source, it is harder for compilers to know what is your workspace without passing in data that wouldn't typically need to be passed to a compiler.

I agree that the analyzer does not handle analyzing conditional imports correctly today. That is something that should be fixed or the conditional import feature should be improved rather than relying on compilers to warn about conditional import bugs. That said, I do not recall ever catching a conditional import issue with a compiler. When I did bad things that the analyzer couldn't catch, the issues always seemed to show up at runtime.

One other datapoint is that for Bazel workspaces, we are already relying on the analyzer to emit lints and warnings as part of compiles (although we probably end up with mostly redundant warnings from the compilers as well).

leafpetersen commented 2 years ago

To be clear, I fully support the compilers emitting errors. My concern is with the compiler emitting non-errors, particularly non-errors that are outside of the scope of the user's workspace like it does now. What we are doing now is similar to if a Java compiler emitted warnings from analyzing .jar files you imported.

No, what we are doing now is similar to gcc emitting warnings if run gcc -Wall. Again, pretty much every compiler toolchain that I am aware of works this way. I dug into this a bit recently in an email thread:

Rust surfaces all errors, warnings, and lints through the compiler. Swift, I believe surfaces errors and warnings in the compiler. Java surfaces Typescript surfaces everything as an error (including things like dead code), but you can disable specific errors. Javascript defines errors and warnings, I presume all emitted by the compiler? C, C++ all provide errors and warnings from the compiler, with linters usually shipped as after market accessories. Java issues errors and warnings through the compiler (dead code is on by default, but many others can be enabled on the command line) Kotlin seems to provide errors and warnings from the command line compiler.

One other datapoint is that for Bazel workspaces, we are already relying on the analyzer to emit lints and warnings as part of compiles

I would say that this is something we should fix.

jacob314 commented 2 years ago

@leafpetersen and I discussed this a bit more.

Long term we are in agreement that the ideal workflow is an integrated Dart Analyzer + CFE that would enable compilers to can optionally show warnings and lints with compiler output with 100% consistency with what IDEs and dart analyze show. Short term, it makes the most sense to remove all warnings from the compiler output as moving to a consistent model without integrating the Analyzer and CFE would be more trouble than it is worth.

scheglov commented 2 years ago

The analyzer supports conditional imports. The code below prints b.dart as the imported path, but will switch to a.dart if you set my.flag to false.

import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/file_system/memory_file_system.dart';
import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/src/test_utilities/mock_sdk.dart';

void main() async {
  final resourceProvider = MemoryResourceProvider();

  final sdkRoot = resourceProvider.getFolder('/sdk');
  createMockSdk(
    resourceProvider: resourceProvider,
    root: sdkRoot,
  );

  resourceProvider.newFile('/home/test/lib/a.dart', r'''
void f(int a) {
  a as int;
}
''');

  resourceProvider.newFile('/home/test/lib/b.dart', r'''
void f(int a) {}
''');

  var file = resourceProvider.newFile('/home/test/lib/test.dart', r'''
import 'a.dart'
  if (my.flag) 'b.dart';
''');

  final collection = AnalysisContextCollectionImpl(
    includedPaths: ['/home'],
    resourceProvider: resourceProvider,
    sdkPath: sdkRoot.path,
    declaredVariables: {
      'my.flag': 'true',
    },
  );

  final analysisContext = collection.contextFor(file.path);
  final analysisSession = analysisContext.currentSession;

  final unitResult = await analysisSession.getResolvedUnit(file.path);
  unitResult as ResolvedUnitResult;

  print(
    unitResult.libraryElement.importedLibraries
        .map((e) => e.source.fullName)
        .toList(),
  );
}
leafpetersen commented 2 years ago

The analyzer supports conditional imports. The code below prints b.dart as the imported path, but will switch to a.dart if you set my.flag to false.

I stand corrected. How do we surface this to users?

leafpetersen commented 2 years ago

Short term, it makes the most sense to remove all warnings from the compiler output as moving to a consistent model without integrating the Analyzer and CFE would be more trouble than it is worth.

I would phrase this as "more effort than we can afford right now", but otherwise LGTM.

scheglov commented 2 years ago

Well, this is a right question. Because currently we don't.

IIRC we had something like -Dfoo.bar=xyz in dartanalyzer (which we want to remove). Now for conditional imports, but for constant evaluation. But apparently it was removed. Theoretically it could be re-wired into dart analyze and analysis server (which is the implementation behind dart analyze).

For IDEs, we can either devise a way to declare variables in analysis_options.yaml (this is somewhat strange, because this is a file in Git, and other developers might want different values), or have a way for users to declare these variables in some ephemeral way in IDE (e.g. the same -Dfoo.bar=xyz in analysis server options).

Anyway, I just wanted to make it more specific - the analyzer does support conditional imports, but user facing tools don't. Which might be a slightly simpler problem to fix implementation wise, although might cause additional arguments about UX :-)

leafpetersen commented 2 years ago

Anyway, I just wanted to make it more specific - the analyzer does support conditional imports, but user facing tools don't. Which might be a slightly simpler problem to fix implementation wise, although might cause additional arguments about UX :-)

Gotcha, thanks.

Hixie commented 2 years ago

Again, pretty much every compiler toolchain that I am aware of works this way.

Ideally we would be better than the other compiler toolchains, not as bad as them. :-)

leafpetersen commented 2 years ago

Again, pretty much every compiler toolchain that I am aware of works this way.

Ideally we would be better than the other compiler toolchains, not as bad as them. :-)

But certainly not worse. In other words, I strongly believe that what the other toolchains do here is the correct thing to do, and that what we do is not, and I would like to see evidence (or at least an argument) to convince me otherwise.

Hixie commented 2 years ago

My experience with Dart normally (warnings/lints/etc happening live during edit) is way better than my experience with other toolchains (no warnings until I try to compile). I'm not sure I understand the argument for the opposite? :-)

I don't understand under what conditions the warning in the OP is something I would want to see from the compiler.

leafpetersen commented 2 years ago

My experience with Dart normally (warnings/lints/etc happening live during edit) is way better than

I believe that all or most of the other toolchains I described provide this experience. The difference is that they also provide a good experience who are not using the interaction mode you describe.

I have literally never in 30+ years as a programmer heard anyone say "gosh I wish gcc would refuse to emit any useful warnings at all, and instead insist that I run some completely unrelated command so that I could plumb through twice as many build rules in my Makefiles, have a build that's twice as slow, and only get warnings if I remember to run some totally separate command". And yet this is exactly what we end up having to do internally.

We have already encountered people whose experience with Dart was deeply sub-optimal because they didn't even know about the analyzer (I believe the fuschia team was in this state for quite a while). I see no reason whatsoever that someone trying to wire up a build system for Dart is in a better position for being forced to wire up two entirely different tools in order to both compile, and to get warnings. And someone who encounters Dart casually without being embedded in our ecosystem will have a strictly worse experience if their only experience is "run the compiler, it accepts completely stupid code with no warnings, and there's no way to ask for warnings".

In general, I believe it is a very poor idea for Dart as a language to build into its tooling basic assumptions about the context and manner in which people will use the language.

I don't understand under what conditions the warning in the OP is something I would want to see from the compiler.

This is something I always want to see from the compiler. My workflow is not yours. Why do you feel that it is a good idea to go out of our way to make my workflow (which is the workflow that almost every other compiler toolchain in the world supports) impossible? I'm perfectly happy for our compiler to support your workflow and provide a way to disable all warnings, and I'm perfectly fine if you want to set up the embedding of Dart into Flutter to assume that users are always working in an IDE with the analyzer - you know your users. But building this assumption into our basic tools is, in my opinion, a very poor design choice. Simple composable tools are better - they let users remix them in ways that you didn't expect, and that add value that you didn't predict. The more unnecessary complexity and pointless deviations from standard practice that we build into our system, the harder it is for developers to do this.

Hixie commented 2 years ago

I think this argument would make sense if the compiler actually reported all the warnings and lints that the analyzer reported, and did so in a way that could be silenced (meaning both on the command line, and using //ignore). But it doesn't, it gives a tiny subset of warnings, and does so in a way that cannot be silenced.

For context, compiler warnings are currently the number one problem with Flutter 3.0.0. People are migrating to the new version, and getting scary warnings when they run flutter run, even though those warnings are very minor and do not indicate an error (and in many cases the warning isn't even actionable, it's in their dependencies). I've literally seen people talk about downgrading from Flutter 3 because of this.

Hixie commented 2 years ago

(FWIW, I do wish gcc would not emit any warnings at all, and instead just had a separate command that didn't need to use build files at all, allowing the build to be faster since gcc could skip checking warnings, and allowing me to get warnings when I want to, by running a separate command. The way Dart does.)

leafpetersen commented 2 years ago

I think this argument would make sense if the compiler actually reported all the warnings and lints that the analyzer reported, and did so in a way that could be silenced (meaning both on the command line, and using //ignore). But it doesn't, it gives a tiny subset of warnings, and does so in a way that cannot be silenced.

Yes, this is what is meant in this comment by "integrated Dart Analyzer + CFE that would enable compilers to can optionally show warnings and lints with compiler output with 100% consistency with what IDEs and dart analyze show". The lack of resources to do this is why I am ok with us taking the expedient but unfortunate step of removing warnings altogether.

(FWIW, I do wish gcc would not emit any warnings at all, and instead just had a separate command that didn't need to use build files at all, allowing the build to be faster since gcc could skip checking warnings, and allowing me to get warnings when I want to, by running a separate command. The way Dart does.)

Ok, I will increment my count of people who would like this to... 1. :)

Hixie commented 2 years ago

I'm definitely in favour of supporting a mode where the compiler appears to do everything the analyzer does now. I mean, we could do that pretty easily by just running the analyzer in parallel when you do dart run or flutter run (and, to the conditional imports point, passing it all the context that the compiler has now).

There's a world where the compiler is entirely silent except for fatal errors, a world where it's as detailed as the analyzer (but can be controlled), and a world where the analyzer doesn't exist and the compiler is the only source of feedback. These are I think reasonable points in the design space. The problem is that slightly away from these points, e.g. where the compiler reports a tiny subset of warnings, if dramatically worse. IMHO it looks like this (vertical axis is "goodness", horizontal axis is this "design space"):

untitled

The dramatic drop on the far left of this graph is where we are now.

The lack of resources to do this is why I am ok with us taking the expedient but unfortunate step of removing warnings altogether.

It sounds like I may be preaching to the choir. :-)

Hixie commented 2 years ago

It's fascinating to see how these warnings are confusing people. https://github.com/flutter/flutter/issues/103561 shows people thinking the warnings are fatal and/or a serious issue with upgrading flutter. https://github.com/flutter/flutter/issues/103571 shows people getting distracted by the warnings and missing the much more critical errors.

jacob314 commented 2 years ago

I've filed https://github.com/dart-lang/sdk/issues/49009 to track addressing the gap where the analyzer shows fewer warnings and errors than the compiler for config specific imports.

@johnniwinther can you drive removing all compiler warnings from the CFE? That will get us out of our current local minima and to the "only analyzer" local minima in @Hixie's diagram. At some point in the future if we unify the CFE and Analyzer, we can then move to the global maxima in the center of the digram with the full set of warnings and lints in all tools.

johnniwinther commented 2 years ago

We already have --verbosity=error to avoid emitting warnings. Why is that not enough?

jacob314 commented 2 years ago

If we make --verbosity=error the default for all users, then either one of two things will be true:

  1. We might as well remove the warning code from the CFE as no users are seeing the warnings.
  2. Some users are still seeing the warnings and package authors will continue to take action to ensure users don't get spooked by warnings that aren't relevant for them.
Hixie commented 2 years ago

If the warnings are exclusively a subset of what the analyzer gives now, then I think defaulting it to "error" and then moving to a world where the compiler and the analyzer agree on what non-fatal messages to report on the long term seems reasonable. If the compiler gives any warnings that are not given by the analyzer, then I don't think we would want to silence those until the analyzer can give them, right?

Hixie commented 2 years ago

(just removing all the warnings that are also given by the analyzer is fine by me too, if we don't think we'll ever have the bandwidth to make the compiler and the analyzer agree on what warnings to show)

natebosch commented 2 years ago

One other datapoint is that for Bazel workspaces, we are already relying on the analyzer to emit lints and warnings as part of compiles

I would say that this is something we should fix.

What problems do you think the current approach causes? Even if the compiler could emit all of the diagnostics I think we'd still invoke it twice to keep the more expensive static analysis off the critical path.

I don't have a very wide sample of languages, but I can't recall a project I've worked on professionally where we didn't use some static analysis tool in addition to the compiler. Do any other languages used at Google rely solely on their compiler for static analysis?

leafpetersen commented 2 years ago

If we make --verbosity=error the default for all users, then either one of two things will be true:

  1. We might as well remove the warning code from the CFE as no users are seeing the warnings.

This concern seems orthogonal to this issue.

leafpetersen commented 2 years ago

I don't have a very wide sample of languages, but I can't recall a project I've worked on professionally where we didn't use some static analysis tool in addition to the compiler.

And I can't recall any system that I've worked with where the compiler didn't emit warnings. Concretely, I gave a long list of languages for which it does above.

Do any other languages used at Google rely solely on their compiler for static analysis?

I don't know, do they?

leafpetersen commented 2 years ago

Do any other languages used at Google rely solely on their compiler for static analysis?

I don't know, do they?

More concretely, do any other languages used at Google explicitly disable all warnings from the compiler?

bwilkerson commented 2 years ago

Most languages that I'm aware of have compilers that produce errors and warnings, and depend on third party linters to provide additional checks. Perhaps what's unique in our case is that the analyzer produces both the errors and warnings that the compiler would traditionally produce as well as all of the lints that a traditional linter would produce.

Personally, I find this to be a benefit, especially in a language that doesn't require a separate compilation step before the code can be executed.

I wonder whether the discussions around removing the concept of "lints" (in favor of calling them "warnings") has caused confusion in this discussion. In other words, I wonder whether there's a useful distinction between diagnostics produced by the compiler (errors and warnings) and diagnostics produced outside of the compiler (lints).

leafpetersen commented 2 years ago

Most languages that I'm aware of have compilers that produce errors and warnings, and depend on third party linters to provide additional checks. Perhaps what's unique in our case is that the analyzer produces both the errors and warnings that the compiler would traditionally produce as well as all of the lints that a traditional linter would produce.

There are a set of people on this thread who are advocating, as a position of principle, that the compiler should be forbidden from ever emitting warnings (e.g. @Hixie has endorsed that principle above). I do not agree with this position. I think that there is value in having the compiler be able to emit a core set of diagnostics, even if we provide modes of running which default the warnings to off, and even if we provide separate tooling that provides additional diagnostics. I'm fairly surprised that this is a controversial position given that it is the behavior of every single other toolchain that I've looked at, and that it does not prevent any of the use cases that are available in the "forbid the compiler from emitting warnings" world (you can always just have the compiler not emit non-error diagnostics).

The state that seems most desirable to me, and that I believe we should be working towards is the following:

I understand that getting to that state (if we agree that that is the desirable state) may take time, and that in the meantime, we may need to make expedient choices.

@jacob314 is concerned about users getting warnings from packages that they do not control, and would prefer to either remove the warning code paths for now, or else to ship our compilers with warnings disabled in both Dart and Flutter. The former seems somewhat unfortunate if we do in fact move towards a world in which the cfe and the analyzer share a static analysis engine, but there is also an argument for not keeping the code around if it is almost never exercised by real users.

I suspect that there is no ideal short term solution here, but given that there is a solution which can be shipped now with no code changes (setting verbosity to error), I would suggest starting with that? And then perhaps @jacob314 can work with the CFE team to figure out the best path forward for the medium term?

bwilkerson commented 2 years ago

I don't really understand the value in separating the Dart and Flutter lint engines, but I do agree that the first step is to decide what the desired end state is.

devoncarew commented 2 years ago

I'd like to point out - in case it's been lost in the discussion - that compilers just process (and emit warnings, errors, ... for) the code that's been asked for - the transitively referenced code. A static analysis tool - a linter, a language server, ... will analyze all the code in the project. This is an important difference; a compiler - even one that could emit warnings and lints - wouldn't be able to serve the same uses cases as a more general purpose linter - keeping a codebase lint free, ...

Hixie commented 2 years ago

I'm fairly surprised that this is a controversial position given that it is the behavior of every single other toolchain that I've looked at

At least for me, what other languages and toolchains do is largely irrelevant. I want to generate something qualitatively better than everything else. This means questioning all the default assumptions of other systems and coming up with designs independently. If we happen to end up in the same place fine, but that wouldn't be because of what the other toolchains do. An argument based on drawing parallels to other systems is not compelling to me.

As noted above, though, I have no objection to the compiler having a mode where it does the same thing as the analyzer. It would be easy to do that today (just run the analyzer in parallel when starting the compiler). My objection is to the status quo which is IMHO literally the worst possible world (see my comment above with the graph).

leafpetersen commented 2 years ago

At least for me, what other languages and toolchains do is largely irrelevant. I want to generate something qualitatively better than everything else.

It is not irrelevant, because it is an existence proof that all of the issues raised here can, and have been, solved before in a manner completely consistent with allowing compilers to emit warnings, and in a manner that has proven itself over and over again to scale well across numerous different platforms. It's fine if you want to do something qualitatively better, but the burden of demonstrating that what you want is in fact better, is on you.

To sum up one more time (and this really is the last, I need to move on from this thread because it is not going anywhere):

I don't really know what else to say here - that is my substantive position, and I haven't heard anyone actually engage with that position yet. As I've said above, I'm fine if for expediency we choose to plumb through the option to turn off compiler warnings in the flutter toolchain, and I'm reluctantly ok with us choosing to rip these code paths out of the compiler if that's our best option given our short-term needs. But I nonetheless maintain that doing is a long term net negative for the project as a whole, for the reasons which I list above, and I have not heard anyone propose even a counter-hypothesis for why this is not the case.

Hixie commented 2 years ago

It's not clear to me what position you are arguing against. Are you saying the status quo is what you want? Or are you saying the center of my graph is what you want?

lrhn commented 2 years ago

Practically, we have the issue that the analyzer and a compiler have different optimization goals.

The analyzer, and the analysis server in particular, must work on completely broken programs, because it runs continuously while code is being written. Recovery is paramount. It also helps people while writing, which means analyzing all the available code in the repository, and in dependencies, not just the code being compiled (as pointed out before), because that allows suggesting imports before they are actually added. The stand-alone analyzer also does full-directory/package analysis, not just "transitively reachable from entry-point" analysis.

The compiler assumes a closed world where, at least, missing a dependency is a compile-time error, not an opportunity to suggest a quick-fix.

That said, a compiler could try to compile until hitting the first compile-time error, then fall back an give the program to the analyzer for a full and useful report of the problems. That would allow fast compilation of error-free programs, and slightly slower error reporting of erroneous programs than just running the analyzer directly. The difference might grow when both the compiler and the analyzer starts running macros too.

That would also have the advantage of always giving the same error message for the same error. Currently front-end and analyzer give different error messages for the same error. (Look at our error-tests with [cfe] and [analyzer] expectations. They're not the same messages, and not even in the same place. It should be considered a waste of engineering effort to implement the same error report twice.)

A shared front-end where the compiler can hand-over all the existing artifacts to the analyzer and let it continue, would be nice, but again the code may be optimized for different things, and therefore internal data structures are not necessarily the same. Forcing them to be the same could be detrimental to at least one of the uses.

(Also, @Hixie's drawing missed the fourth option on the circle: No warnings on either analyzer or compiler, which isn't itself interesting, but the area near it is, where neither is complete, and they both provide warnings that the other doesn't. It's interesting, not because it's desirable, but because it's not, and yet it's an incredibly easy failure mode to end up in if things are not well coordinated. And arguably, it's where we are as soon as the analyzer is missing just one required warning or error. That one was fixed, but it was open in recent history.)

So, when we are talking about goals:

Aka, the obvious. What does that mean then?

Compilers being fast sometimes means being modular. That brings even more complication to the equation. It also means being easily integratable into an automatic build-system and continuous integration framework. And providing consistent and machine-readable error reports (at least consistently formatted so you can recognize warnings and errors automatically).

Good developer experience depends on the developer. I think our current analyzer and analysis-server strategy is working well for IDE-users. For command-line users, analyzing your pub package with dart analyze is also fine. For anyone not writing a pub package, and doing it without an IDE (which is more likely if you don't have a pub package, because then it's probably just a single script file) is not well covered. You have to dart analyze filename.dart to analyze it, and just dart run filename.dart doesn't give you nearly as good warning messages. I'm not sure I worry deeply about that use-case (even though I'm precisely the kind of person who writes Dart scripts rather than bash scripts).

My personal preference would be:

The user experience would be that of "compilers give warnings", but without our tools needing to duplicate effort, and optimizing compilation for the case where everything works correctly (which it does most of the time in continuous integration).

jacob314 commented 2 years ago

I've filed https://github.com/flutter/flutter/issues/103994 to track taking the short term expedient step of hiding the warnings from compilers for Flutter. I tend to agree that the center of @Hixie's graph would be the long term ideal maxima but I don't expect we can reach it in the short term. I was too brusk about suggesting we should remove warnings from the compiler. I trust the CFE team to decide whether that logic is worth leaving in the CFE given short term and long term plans.