Skip to content
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] Analyzer doesn analyze part of the library #54661

Open
sgrekhov opened this issue Jan 18, 2024 · 34 comments
Open

[analyzer] Analyzer doesn analyze part of the library #54661

sgrekhov opened this issue Jan 18, 2024 · 34 comments
Labels
area-analyzer Use area-analyzer for Dart analyzer issues, including the analysis server and code completion. P3 A lower priority bug or feature request type-bug Incorrect behavior (everything from a crash to more subtle misbehavior)

Comments

@sgrekhov
Copy link
Contributor

sgrekhov commented Jan 18, 2024

We have the following

// test.dart
library test_lib;
part 'part.dart';

var foo;

main() {
  foo = 1;
  print(bar);
}

// part.dart
part of test_lib;

final foo = "foo";
final bar = "bar";

Analyzer:
dart analyze test.dart reports no issues.
dart analyze part.dart reports The name 'foo' is already defined.
VM:
dart test.dart reports

part.dart: Error: 'foo' is already declared in this scope.
final foo = "foo";
      ^^^
test.dart: Context: Previous declaration of 'foo'.
var foo;
    ^^^
test.dart: Error: Can't assign to this.
  foo = 1;
      ^

Analyzer should report that part 'part.lib'; has some problems preventing execution

Tested on Dart SDK version: 3.4.0-edge.ffe2d1cf84bda4f4edcc9550817b7c033c76f8f8 (main) (Fri Jan 12 10:33:53 2024 +0100) on "linux_x64"

@sgrekhov sgrekhov added the area-analyzer Use area-analyzer for Dart analyzer issues, including the analysis server and code completion. label Jan 18, 2024
@eernstg
Copy link
Member

eernstg commented Jan 18, 2024

(I haven't performed any experiments with file names, assuming it is all *.dart).

That's surprising! How would dart analyze part.dart know which library to use as the "owner" of part.dart if we also have this?:

// test2.dart
library test_lib;
part 'part.dart';

var foo2; // No name clashes.

main() {
  foo2 = 1;
  print(bar);
}

@sgrekhov
Copy link
Contributor Author

Sorry for the typo. I mean part.dart of course. Updated

@bwilkerson
Copy link
Member

Analyzer should report that part 'part.lib'; has some problems preventing execution

I'm assuming you mean that you think these diagnostics should be reported when analyzing the library that includes the part (test.dart in your example).

The documentation isn't clear on this point, but when you use dart analyze to analyze a single file it will report all of the diagnostics reported against code in that file. If the file happens to be a library with parts, the diagnostics reported against those parts will not be included in the output. It appears that it's working as intended.

How would dart analyze part.dart know which library to use as the "owner" of part.dart if we also have this?

It doesn't. It gets very confused. That's a big part of why we now allow, and encourage all users to use, URIs in the part of directive rather than library names. It prevents exactly this kind of confusion. I'm hoping that in a future version of Dart we can remove the ability for a part of directive to use a library name. I'd be even happier if we did so by removing the concept of parts, but that might be too much to hope for.

@sgrekhov
Copy link
Contributor Author

The documentation isn't clear on this point, but when you use dart analyze to analyze a single file it will report all of the diagnostics reported against code in that file. If the file happens to be a library with parts, the diagnostics reported against those parts will not be included in the output. It appears that it's working as intended.

@eernstg do you agree?
@davidmorgan it affects https://dart-review.googlesource.com/c/sdk/+/345541.

@pq pq added the P3 A lower priority bug or feature request label Jan 18, 2024
@davidmorgan
Copy link
Contributor

Thanks everyone! This looks complicated...

Per Sergey's comment (and the PR link), I'm coming at this from the point of view of the language tests, where I am changing the test runner to pick up error expectations from files beyond just the main test file.

compilation_t01.dart is a relevant test, it imports a broken part (duplicate definition) and so we can think about whether the error is reported in the part.

The command line used by the language tests does report the error:

DART_CONFIGURATION=ReleaseX64 out/ReleaseX64/dart-sdk/bin/dart out/ReleaseX64/gen/dartanalyzer.dart.snapshot -Dtest_runner.configuration=analyzer-asserts-linux --ignore-unrecognized-flags --packages=/usr/local/google/home/davidmorgan/git/dart-sdk/sdk/.dart_tool/package_config.json --format=json /usr/local/google/home/davidmorgan/git/dart-sdk/sdk/tests/co19/src/Language/Libraries_and_Scripts/Parts/compilation_t01.dart
{"version":1,"diagnostics":[{"code":"duplicate_definition","severity":"ERROR","type":"COMPILE_TIME_ERROR","location":{"file":"/usr/local/google/home/davidmorgan/git/dart-sdk/sdk/tests/co19/src/Language/Libraries_and_Scripts/Parts/part_0.dart","range":{"start":{"offset":291,"line":12,"column":7},"end":{"offset":294,"line":12,"column":10}}},"problemMessage":"The name 'foo' is already defined.","correctionMessage":"Try renaming one of the declarations.","contextMessages":[{"location":{"file":"/usr/local/google/home/davidmorgan/git/dart-sdk/sdk/tests/co19/src/Language/Libraries_and_Scripts/Parts/compilation_t01.dart","range":{"start":{"offset":824,"line":17,"column":451},"end":{"offset":827,"line":17,"column":454}}},"message":"The first definition of this name."}],"documentation":"https://dart.dev/diagnostics/duplicate_definition"}]}

and equivalent output with --format=machine or no --format arg. This command refuses to analyze the part by itself: "part_0.dart is a part and cannot be analyzed. Please pass in a library that contains this part."

Then, dart analyze is different, as Brian said "the diagnostics reported against those parts will not be included in the output":

dart analyze dart analyze tests/co19/src/Language/Libraries_and_Scripts/Parts/compilation_t01.dart --> no errors reported

Where it gets interesting is

dart analyze tests/co19/src/Language/Libraries_and_Scripts/Parts/part_0.dart

which does find and report the error, despite there being no single owner library. Playing around a bit it looks like it takes the first alphabetical owner library and ignores the rest; so if I add a duplicate definition to compilation_t04.dart, an alternative owner library, then it is not reported; unless I make compilation_t01.dart not an owner library, then the error I introduce in compilation_t04.dart is reported.

Aside: amidst all this complexity there is a trivial bug affecting the output, which is that the JSON gets the file of the context message right but the human-readable version does not:

"contextMessages":[{"message":"The first definition of this name.","location":{"file":"/usr/local/google/home/davidmorgan/git/dart-sdk/sdk/tests/co19/src/Language/Libraries_and_Scripts/Parts/compilation_t04.dart","offset":856,"length":3,"startLine":17,"startColumn":483,"endLine":17,"endColumn":486}}]
  error • part_0.dart:16:5 • The name 'bar' is already defined. Try renaming one of the declarations. • duplicate_definition
           - The first definition of this name at part_0.dart:17:483.

Okay, now some thoughts on what we should actually do here ;)

The language tests are not testing dart analyze, they're testing a different entrypoint. There is a difference in how part files are handled. As far as I can see the current way is better for the language tests: it reports more errors, so we can assert on more errors (once my test runner change lands); and it works always from the library, so there is no confusion about which file owns the part.

Then I think there are three things we need to do:

  • dart analyze output isn't the right way to think about the language tests as it's not equivalent to what the tests do, we should switch to a different command. (Is dart dartanalyzer.dart.snapshot the only/best way?)
  • Decide if there is any issue with the current "dart analyze" behaviour or if it's working as intended / will not fix.
  • If desired, file an issue / follow up on the wrong human-readable output issue noted above.

How does that sound, please? :)

@sgrekhov
Copy link
Contributor Author

My testing also confirms all of the above. If we preserve the current behaviour of the tools, then, I think, with the updated test runner the part tests should be rewritten as:

// test1.dart
import 'part1.dart`;

main() {...}

// part1.dart
library some_lib;

  Line of code containing error;
//^
// [analyzer] unspecified
// [cfe] unspecified

// test2.dart
library test_lib;
part 'part.dart';

main() {
...
}

// part.dart
part of test_lib;

  Line of code containing error;
//^
// [cfe] unspecified

For test1.dart the test runner expects errors in both CFE and analyzer, but for test2.dart tests should expect error in CFE only because analyzer doesn't 'dive' into files specified in part directive.

@davidmorgan
Copy link
Contributor

It's true that dart analyze run on the test file on the command line doesn't report anything for the part file, but the analyzer used in other ways (run on the whole folder, running in the IDE, run via the different command line I gave) does report something; so I think it's useful to test it in the language tests, as it should cover those ways of using the analyzer.

Maybe this part of the discussion would better belong on the test_runner feature request, but we seem to have mostly the same people anyway :)

@davidmorgan
Copy link
Contributor

@eernstg @bwilkerson @munificent not sure who is the right person / people to decide on what the language tests do here? Input appreciated please :) thanks!

@bwilkerson
Copy link
Member

... not sure who is the right person / people to decide on what the language tests do here?

I'm sure it's "people", not just "person". And I think I'm the right representative from the analyzer team. But I agree we need someone from the language team and maybe someone from the infrastructure team, depending on how big a change we might want there.

The command line used by the language tests does report the error ...

The language tests are currently using the dartanalyzer tool. There is a strong desire (from the analyzer team at least) to remove that tool so that we have less code to maintain. The biggest difference I was previously aware of between that tool and the newer dart analyze tool is that dartanalyzer supports a mode in which the test runner can feed it files to analyze over stdin rather than on the command line. We know that we need to add that support to dart analyze (because dropping it would increase the time to run tests by far too much), but haven't been able to make the time to do so yet.

It's possible that we need to support reporting diagnostics from part files as part of the same support (that is, we could consider reporting diagnostics from part files only when in "test runner" mode).

Decide if there is any issue with the current "dart analyze" behaviour or if it's working as intended / will not fix.

I think this needs to take into consideration that the answer might differ depending on whether we're asking about the behavior for users or the behavior for the test runner.

The behavior isn't consistent with how the tests are currently written, so we either need to change

  • the behavior of dart analyze so that diagnostics from parts are reported when analyzing the library, or
  • the way the tests are written so that we don't include parts in multiple libraries.

I know that using the same part file from multiple libraries is convenient because it means fewer duplicated files, making both the writing and maintaining of tests easier. But there isn't, as far as I'm aware, any valid language semantics for this kind of sharing, which means that the analysis server can't be used when editing these files. If we'd like people to be able to open the test directory (or some subset thereof) in an IDE, then I think the current test structure is going to be an issue. I don't want to have to support a feature that might go away for other reasons.

And while we're at it, I'd like to add one more factor to the discussion: how are we planning on writing tests for macros and library augmentations (with appropriate caveats: assuming we support macros / library augmentations)? Are we going to want to analyze the augmented library and have diagnostics from the library augmentation(s) be reported, or will we analyzer the library augmentations separately? (My current expectation is that dart analyze would continue to operate as it does today and you'd only see diagnostics for the files that are explicitly included on the command-line, but that might also need to be different for the test runner.)

If desired, file an issue / follow up on the wrong human-readable output issue noted above.

Yes please. That's a bug no matter what else we decide. I don't know how much impact it's having on users, but we should at least record it so that it isn't forgotten.

@davidmorgan
Copy link
Contributor

Thanks Brian! Some thoughts from my side.

... not sure who is the right person / people to decide on what the language tests do here?

I'm sure it's "people", not just "person". And I think I'm the right representative from the analyzer team. But I agree we need someone from the language team and maybe someone from the infrastructure team, depending on how big a change we might want there.

SGTM

The command line used by the language tests does report the error ...

The language tests are currently using the dartanalyzer tool. There is a strong desire (from the analyzer team at least) to remove that tool so that we have less code to maintain. The biggest difference I was previously aware of between that tool and the newer dart analyze tool is that dartanalyzer supports a mode in which the test runner can feed it files to analyze over stdin rather than on the command line. We know that we need to add that support to dart analyze (because dropping it would increase the time to run tests by far too much), but haven't been able to make the time to do so yet.

It's possible that we need to support reporting diagnostics from part files as part of the same support (that is, we could consider reporting diagnostics from part files only when in "test runner" mode).

That all sounds fine--as far as I can see it does not need to block extending the language tests using the current (deprecated) dartanalyzer tool?

Stepping back a bit--is there consensus that we want the language tests to cover (and so, be able to assert on) diagnostics in parts?

Decide if there is any issue with the current "dart analyze" behaviour or if it's working as intended / will not fix.

I think this needs to take into consideration that the answer might differ depending on whether we're asking about the behavior for users or the behavior for the test runner.

The behavior isn't consistent with how the tests are currently written, so we either need to change

  • the behavior of dart analyze so that diagnostics from parts are reported when analyzing the library, or
  • the way the tests are written so that we don't include parts in multiple libraries.

I know that using the same part file from multiple libraries is convenient because it means fewer duplicated files, making both the writing and maintaining of tests easier. But there isn't, as far as I'm aware, any valid language semantics for this kind of sharing, which means that the analysis server can't be used when editing these files. If we'd like people to be able to open the test directory (or some subset thereof) in an IDE, then I think the current test structure is going to be an issue. I don't want to have to support a feature that might go away for other reasons.

Having the tests stop using multi-owner parts, except where that is intentional to add test coverage for it, sounds reasonable to me and would certainly simplify the question. @sgrekhov what do you think please, would that be feasible/worthwhile?

And while we're at it, I'd like to add one more factor to the discussion: how are we planning on writing tests for macros and library augmentations (with appropriate caveats: assuming we support macros / library augmentations)? Are we going to want to analyze the augmented library and have diagnostics from the library augmentation(s) be reported, or will we analyzer the library augmentations separately? (My current expectation is that dart analyze would continue to operate as it does today and you'd only see diagnostics for the files that are explicitly included on the command-line, but that might also need to be different for the test runner.)

I expect we'll want the language tests to have at least the option to cover all the types of diagnostics that might fire, whenever they fire; so I think, yes, a "report all the diagnostics" mode would be good.

If desired, file an issue / follow up on the wrong human-readable output issue noted above.

Yes please. That's a bug no matter what else we decide. I don't know how much impact it's having on users, but we should at least record it so that it isn't forgotten.

Done: #54717

Thanks.

@munificent
Copy link
Member

munificent commented Jan 24, 2024

Given:

// a.dart
import 'b.dart';
import 'c.dart';

main() {
  foo();
}

// b.dart
void foo() {
  print('b foo');
}

// c.dart
void foo() {
  print('c foo');
}

If I run:

$ dart analyze a.dart

I get:

error • a.dart:5:3 • The name 'foo' is defined in the libraries 'file:///Users/rnystrom/Documents/b.dart'
        and 'file:///Users/rnystrom/Documents/c.dart'. Try using 'as prefix' for one of the import
        directives, or hiding the name from all but one of the imports. • ambiguous_import

So here I have an example where I'm analyzing a single file that depends on others. The other files have no errors, but in the context of the file I'm analyzing, they lead to an error.

The behavior I see here makes sense to me.

Given that, I don't see why analyzer isn't reporting an error when you analyze a single file that ends up containing an error because of a part file that it includes. I understand this is "working as intended", but why is this the intended behavior?

@sgrekhov
Copy link
Contributor Author

Having the tests stop using multi-owner parts, except where that is intentional to add test coverage for it, sounds reasonable to me and would certainly simplify the question. @sgrekhov what do you think please, would that be feasible/worthwhile?

It's not a problem to update the tests to not use multi-owner parts. I don't think that we have more than 1-2 such tests. But, anyway, we should add the special test for this situation. I'm now waiting for this thread to update the tests accordingly

@bwilkerson
Copy link
Member

Given that, I don't see why analyzer isn't reporting an error when you analyze a single file that ends up containing an error because of a part file that it includes. I understand this is "working as intended", but why is this the intended behavior?

I don't remember the discussion (assuming that there was one), but I suspect that it's because we were thinking of the command-line tool as operating on files. Given that mindset, the following seems natural:

  • If dart analyze is run over a directory, which I suspect is the most common case, then it should report all of the diagnostics for all of the files in that directory, including in subdirectories.

  • If it's run on a single file then it should report only the diagnostics reported in that file.

It's been suggested above that perhaps we should have been thinking about the tool operating on libraries rather than files, and I suppose that's a possibility, but it does raise some questions related to both generated files and uncommon directory structures that the more simplistic approach provides a clear answer for.

@eernstg
Copy link
Member

eernstg commented Jan 25, 2024

I've been OOO a few days, and this issue doesn't seem to have a straightforward solution, so I didn't get around to say anything here for a while. I still think it's difficult. ;-)

However, let me outline the main point as I see it:

Assume that static analysis is performed on a part file whose "owner" library isn't known (because it uses a library some_name; directive rather than library 'some_URI';, and it wasn't included in the analysis based on a part directive in some library). Assume that an owner library is chosen heuristically.

In this situation there is no meaningful limit on the discrepancies that may occur relative to a program where that part is actually included: Name resolution may differ (an identifier may refer to an inherited variable declaration according to the analyzer, but it refers to a top-level function declaration in the actual program, or vice versa; or the name may be undefined in one case and defined in the other). Expressions may then have completely different types (and semantics) according to the analyzer and in the actual program. Consequently, compile-time errors may exist in one case, but not in the other, or they may be completely different.

In other words, this kind of static analysis may be highly misleading.

For this reason, I'd recommend that this kind of static analysis is clearly classified as such. For example, if the heuristic is being used then a diagnostic message is emitted. It would be unfortunate if anyone gets the impression that the static analysis of Dart is unreliable, based on an encounter with this kind of situation. The diagnostic message would inform the developer that a specific library is being considered as the one that has this part file as a part.

The question is what to do if the choice differs from the developer's intentions. We already have the following behavior:

// --- Library 'test1.dart'

library test_lib;
part 'part.dart';

var foo2;

main() {
  foo2 = 1;
  print(bar);
}

// --- Library 'test2.dart'.

library test_lib;
part 'part.dart';

var foo;

main() {
  foo = 1;
  print(bar);
}

// --- Part 'part.dart'.

part of test_lib;

final foo = "foo";
final bar = "bar";
> dart analyze *.dart
Analyzing part.dart, test1.dart, test2.dart... 0.6s
No issues found!
> dart analyze test1.dart
Analyzing test1.dart...                0.4s
No issues found!
> dart analyze test2.dart
Analyzing test2.dart...                0.4s
No issues found!
> dart analyze part.dart
Analyzing part.dart...                 0.4s
No issues found!
> dart analyze test1.dart part.dart
Analyzing test1.dart, part.dart...     0.4s
No issues found!
> dart analyze test2.dart part.dart
Analyzing test2.dart, part.dart...     0.4s

  error • part.dart:5:7 • The name 'foo' is already defined. Try
          renaming one of the declarations. • duplicate_definition
           - The first definition of this name at part.dart:6:8.

1 issue found.

That is, we can coerce the analyzer into choosing a specific library/part binding by passing the library as well as the part as command line arguments.

However, the fact that there are 'no issues' in all other cases illustrates that it is a somewhat tricky exercise. I believe that a diagnostic message ("this library/part binding was chosen heuristically") could be helpful for developers who are using parts with non-URI part of directives, and it should not make any difference at all for developers who are not.

@eernstg
Copy link
Member

eernstg commented Jan 25, 2024

@bwilkerson wrote:

... the tool operating on libraries rather than files ...

Yes, that's a very interesting idea! I don't know if it would be too disruptive to report all errors on a library and its parts as belonging to the library (that is, they are reported if and only if diagnostics are reported for that library), but I think it would deal with the underspecified library/part binding once and for all.

An IDE could then show all messages for the library and all its parts with each of those files (the library and each of the parts), if it is configured to show diagnostic messages for "current file only".

But the most important part, I'd say, is still the status of the library/part binding: If it is chosen heuristically then it should be communicated to the developer that it might yield completely misleading results.

@davidmorgan
Copy link
Contributor

Hi everyone,

There are some quite tangled issues here :)

My own interest is to get unblocked on this PR expanding the assertion power of the static error tests in the language test suite to include files beyond just the test file.

To that end I wonder if we can cut through the issues just for the case of these tests by saying that we want the static error tests to have maximal assertion coverage: that we would like the asserts to be about the union of diagnostics the analyzer can produce if used correctly, via any entrypoint, on the test case.

This seems to me to be the right thing to do to maximize the value of the tests.

The case of multi-owner parts we would handle pragmatically by refactoring those cases so we are back in the territory of defined behaviour.

I think that would be enough to unblock me. Even if my PR is not perfect--maybe I missed some case where we could usefully assert on a diagnostic and the tests still don't cover it--it certainly does expand coverage to new useful places; and with the stated intention that we maximize assertion coverage, we can fix any such cases if as we find them.

@eernstg @bwilkerson @munificent how does that sound, please? :)

@eernstg
Copy link
Member

eernstg commented Jan 29, 2024

we would like the asserts to be about the union of diagnostics the analyzer can produce if used correctly, via any entrypoint, on the test case.

Sounds good!

I'm not 100% sure I understand what it will do, though. ;-)

I'd interpret 'entry point' to mean the same thing as 'script' in the language specification, i.e., a library that exports a main method with a certain kind of formal parameter list. I think that's consistent with the use of 'entry point' in the build_runner, and possibly elsewhere. However, in this case it might mean "any library which is specified to be an analysis target" (that is, a library whose faults are reported, not just a library which is needed because it's imported by a target, directly or indirectly).

(If we're supposed to obtain error messages from a part then the part should be specified as a target as well, along with exactly one library that has this part file as a part. But I guess all the part issues have been postponed right now.)

@bwilkerson
Copy link
Member

To that end I wonder if we can cut through the issues just for the case of these tests ...

Thanks for the reminder. I had kind of lost sight of the need for a quicker answer.

The test running is currently using the dartanalyzer tool. It's the only known use of that tool at this point (my understanding is that it isn't shipped with the SDK, even though it's built with the SDK). I'm absolutely fine with being totally pragmatic with dartanalyzer and letting you make any changes that will unblock you.

The interesting question is what we need to do in the longer term to dart analyze, the test runner, and any other parts of the test infrastructure in order to allow us to delete the dartanalyzer from the SDK. But even then, if we need to do something for the test runner that isn't exposed to users, I'm ok with that too.

@sgrekhov
Copy link
Contributor Author

To confirm.

  1. Do I understatnd right that tests should be rewritten assuming that the analyzer doesn't analyze parts?
  2. If part has multiple "owners" what should be an expected behaviour? Say, part.dart is happy with owner1.dart but has an error with owner2.dart (say owner2.dart declares a variable with the same name as in part.dart). What then should we expect from the analyzer in the case when we are analyzing part.dart?

@eernstg
Copy link
Member

eernstg commented Jan 30, 2024

I think it would be nice to avoid the multi-owner-part issue for now. We could do that by changing every test where the part does not have a unique owner such that it has a unique owner. We should probably still have both <uri> based part-of directives and name based part-of directives, but with unique library this.is.my.library.name; names. Next, we could have just a single test with a part that has a name based part-of directive and multiple owners. We might want to mark that one as skip in the relevant status files and create an issue such that we will know that it should be handled later on.

About assuming that the analyzer doesn't analyze parts: We do know that dart analyze theOwner.dart thePart.dart will report errors in thePart.dart considered as a part of theOwner.dart (if theOwner.dart has the corresponding part directive such that it can be the owner). Isn't it possible to tweak the test runner such that it will do a similar thing? @davidmorgan, is your ongoing work going to enable this, if we don't already have it?

@bwilkerson
Copy link
Member

Do I understatnd right that tests should be rewritten assuming that the analyzer doesn't analyze parts?

I would characterize this differently. I think that the upshot is that the test framework and dart analyze do not work the same. If you're writing tests today, then I think you can assume that both a library and its parts will be analyzed and reported on by the test framework (test.py), but don't use dart analyze to analyze the tests.

There is an ongoing discussion to figure out what the correct behavior is for the future, but there isn't yet consensus on what that will be.

@sgrekhov
Copy link
Contributor Author

sgrekhov commented Feb 5, 2024

I'm ready to update co19 tests according to the above. @davidmorgan is it possible rebase https://dart-review.googlesource.com/c/sdk/+/345541? Otherwise it produces an error during the build of the SDK and I can't use the change for the testing

@davidmorgan
Copy link
Contributor

Sounds good @sgrekhov, thanks! Done. I made a few more tweaks to the PR that are not ready for review yet, in particular to show the file name in failure output, but they should work for your testing.

@sgrekhov
Copy link
Contributor Author

sgrekhov commented Feb 6, 2024

Dart specification (19.5. Parts)

Compiling a part directive of the form part s; causes the Dart system to
attempt to compile the contents of the URI that is the value of s. The top-level
declarations at that URI are then compiled by the Dart compiler in the scope
of the current library. It is a compile-time error if the contents of the URI are
not a valid part declaration

Doen't the specification mean that the contents of the part URI should be analyzed and errors reported (if any)?

@eernstg
Copy link
Member

eernstg commented Feb 6, 2024

the contents of the part URI should be analyzed and errors reported (if any)?

It's somewhat vague. It could be interpreted to mean that the tools must report a not-so-informative error message like "part.dart is not a valid part declaration" if part.dart is determined to be the meaning of s and there are any errors in part.dart. It could also be taken to mean that the regular, detailed error messages reported for part.dart must be reported when the owning library is compiled.

So we probably just need to decide on an approach here, and then I wouldn't be surprised if that approach is already one of the possible interpretations of the given section in the language specification.

@bwilkerson, what do you think about dart analyze reporting the error messages for each part which is a part of 'my_library.dart' when 'my_library.dart' is analyzed? This would automatically ensure that the binding from the owning library to the given part file is well defined. It also seems conceptually justified to me: Each of those part files make sense relative to a given owning library (and they do not make sense if there is no owning library, or it's ambiguous).

@davidmorgan, would your changes to the test runner be compatible with this approach?

@davidmorgan
Copy link
Contributor

@davidmorgan, would your changes to the test runner be compatible with this approach?

Yes, that matches. Thanks.

@bwilkerson
Copy link
Member

what do you think about dart analyze reporting the error messages for each part which is a part of 'my_library.dart' when 'my_library.dart' is analyzed?

Conceptually I have no problem with that. We had convinced ourselves that that wasn't what users want, but if we think that users (other than the test framework) would rather see all the diagnostics then I'm fine with that. (The caveat is there because I'm not willing to compromise the UX to satisfy the test framework when there are other ways for the test framework to be satisfied.)

However, doing so might be an interesting implementation challenge. I say that because the way the command-line dart analyze tool works is by starting up a language server and asking it to analyze the files and directories specified on the command line. The language server analyzes those files and any files reachable from them, then sends back to the client (the command-line tool) all of the diagnostics that were produced in any of those files (but not for files outside the specified list). There currently isn't any way for the command-line tool to tell the server to include diagnostics from part files. Not a blocker, but definitely not as easy as one might hope.

This would automatically ensure that the binding from the owning library to the given part file is well defined. It also seems conceptually justified to me: Each of those part files make sense relative to a given owning library (and they do not make sense if there is no owning library, or it's ambiguous).

If we were talking about a compiler I'd agree. But the analyzer isn't a compiler (as much as it looks like a compiler sometimes). It's the support for our IDE tooling (via the language server). The analyzer does a lot of work to recover from errors in the code. In this particular case, we tolerate a part file being included in multiple libraries by treating each of the enclosing libraries as if it were the sole owner of the part and performing analysis from that perspective [1]. In other words, we support a level of ambiguity (for the sake of the UX) that compilers don't need to tolerate.

(Though I think it's fair to say that the compiler effectively tolerates the same ambiguity in the same way as long as only one of the owning libraries is in the program being compiled, as evidenced by the multiple tests that are sharing a single part.)

[1] We actually go a little too far in that direction. We don't produce a diagnostic telling the user that the part is being included in multiple libraries, which we ought to do.

@eernstg
Copy link
Member

eernstg commented Feb 6, 2024

we tolerate a part file being included in multiple libraries by treating each of the enclosing libraries as if it were the sole owner of the part and performing analysis from that perspective [1]. In other words, we support a level of ambiguity (for the sake of the UX) that compilers don't need to tolerate.

Interesting! But then I'd say that the ambiguity can be eliminated after all: We have one analysis based on (owner1.lib, part.dart) and another analysis based on (owner2.dart, part.dart); we may have errors from 0, 1, or 2 of those analyses; each of them is correct and consistent (for the given choice of owner/part binding). That's great!

The missing bit is that every error which is reported for a location in a part file needs to be emitted together with information about which owner library is the chosen one for this analysis result. This would make some error messages more verbose, but if candidate owner libraries are only selected from the same directory, or the same package, or some other "small" set of libraries then it should be possible to give that information in a rather compact form.

We still can't know that we've found every library which can be the owner of any given part file (we can't search the entire planet), but this might be handled by adding some constraints ("it is a compile-time error to have a part directive pointing to a different package", or something).

For the test runner it still seems natural to me if errors in a part file are reported together with errors in the owning library.

@bwilkerson
Copy link
Member

... it is a compile-time error to have a part directive pointing to a different package ...

The spec has traditionally avoided introducing the notion of a "package", but I wouldn't be surprised if that just failed to work in general.

For the test runner it still seems natural to me if errors in a part file are reported together with errors in the owning library.

For the test runner I expect that it would be natural to report every diagnostic from every reachable file (which would be trivial to implement with what we have today. That's part of the reason that I want to separate the question of what the test runner should do from the question of how the command-line tool should work when invoked directly.

@eernstg
Copy link
Member

eernstg commented Feb 7, 2024

@bwilkerson wrote:

For the test runner I expect that it would be natural to report every diagnostic from every reachable file (which would be trivial to implement with what we have today.

This sounds great!

Would it be a breaking change? Who would need to agree before we can proceed and just do that? @davidmorgan as well, WDYT?

@bwilkerson
Copy link
Member

Would it be a breaking change?

Given that we're only talking about the way dart analyze works when used by our test framework, and the fact that the test framework doesn't yet use dart analyze, I believe that it can't be a breaking change.

There's another piece of functionality that we need in dart analyze before it can be used by the test framework. The old dartanalyzer binary referred to it as "batch" mode, and enabled it with a command-line flag (--batch). The purpose of the flag is to cause the command to read the list of files and directories from stdin rather than from the command-line. In this mode it reads a single line from stdin, performs analysis, prints results, then reads the next line.

My concrete proposal, then, is that we (not necessarily someone on the analyzer team) implement batch mode in dart analyze and that part of the semantics of batch mode is that dart analyze will print all of the diagnostics reported by the language server as a result of analyzing the file (or directory, but I don't think that capability is ever used).

@sgrekhov
Copy link
Contributor Author

sgrekhov commented Feb 8, 2024

For the test runner I expect that it would be natural to report every diagnostic from every reachable file (which would be trivial to implement with what we have today.

@bwilkerson if this a trivial change could you please advice how to do it (which files/classes to see)? I want to try to implement this feature

@davidmorgan
Copy link
Contributor

For the test runner I expect that it would be natural to report every diagnostic from every reachable file (which would be trivial to implement with what we have today.

@bwilkerson if this a trivial change could you please advice how to do it (which files/classes to see)? I want to try to implement this feature

@sgrekhov my intention is to do that as part of my already-in-progress PR for the test runner, which I believe will make the analyzer asserts match the CFE asserts, for the simplest possible testing.

@sgrekhov
Copy link
Contributor Author

sgrekhov commented Feb 8, 2024

@sgrekhov my intention is to do that as part of my already-in-progress PR for the test runner, which I believe will make the analyzer asserts match the CFE asserts, for the simplest possible testing.

Ok, thank you!

@srawlins srawlins added the type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) label Mar 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-analyzer Use area-analyzer for Dart analyzer issues, including the analysis server and code completion. P3 A lower priority bug or feature request type-bug Incorrect behavior (everything from a crash to more subtle misbehavior)
Projects
None yet
Development

No branches or pull requests

7 participants