Repository navigation
Conversation
The reference search misses the uses of an operator in other libraries and relational patterns, and lists every use of Object.== for a == override. Each operator's override hierarchy is now renamed in unsaved copies of its files, one hierarchy per round, and every new error counts as a use. --also operators is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
…the review findings - Collect an operator's hierarchy through every supertype of the declaring type, not only the one member that the super request names, in operator_hierarchies.dart, with the requests in a pool. - Open a file before sending changed content for it, so that an override in a file that the scan skips no longer crashes the run. - Wait for the analysis behind a hover request, which needs the resolved file, instead of a folding range request, which the server can answer before it reports that it is analyzing. - Record a failed lookup once, and report the uses only after the files are restored. - Give every member of a hierarchy the same new name, padded with spaces. - Share the check for a removed also value, which now trims each value. - Update example/README.md with a real run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
…rchy lookups A comparison returns a bool, which no operator of a class, mixin, enum or extension type can take as its receiver, so a failed comparison hides no other use. Hierarchies whose operators all return bool now share a round, one per operator name, and the text of each error says which one it belongs to. The lookups of an operator hierarchy are cached per type, and an override found through the implementation request gets no request of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
- LspClient.didChange opens a document that isn't open, instead of UnsavedChanges opening it before each change. - The call and operator lookups share one UnsavedChanges, and withImplicitUses restores the files once. UnsavedChanges.show replaces change, restoreExcept and revert. - Both lookups return an entry for every candidate that they checked, so withImplicitUses merges them in one way. - One DeclarationName class replaces OperatorDeclaration and the record for a type's name, the root check uses analysisRootContains, and the symbol lookups use symbolCovering and symbolNamedAt, which move to symbols.dart from superclasses.dart. - The rename of each hierarchy is computed once, and the rule for shared rounds sits next to the rounds. - The also values are split in one place, in option_values.dart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
- Restore the files that the call lookup changed before the operator lookup asks about positions in them, and send the content on disk in a finally. - Merge the hierarchies that share a member, so that the result doesn't depend on the order of the operators. - Leave out == before any lookup: it always overrides Object.==. - Share rounds only between hierarchies whose members all return bool by their outlines, and never for [] and []=, whose errors cover the index. - Leave out a hierarchy when a line break or an offset outside the content on disk would move positions in the rename, instead of crashing or shifting lines. - Use classes instead of record typedefs, a pattern instead of a when guard, one text for the operators advice, and restore the doc comment of PositionGeometry. - MIGRATION.md says that library code drops FinderOptions.skipOperators. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
- Plan the operator rounds before the call lookup changes any file, so the files are no longer restored and analyzed between the two lookups. - Rename the operator keyword and blank the name through the semantic tokens, which leaves line breaks and comments in between as they are, so such operators are checked too. - Share the outline, symbol and token lookups: lastCovering, Outline.namedAt (moved from overrides.dart), lastCodeBefore, SourceIndex.offsetsOf and contentWith, which the call lookup uses as well. - Record a failed restore whenever it happens, with one helper that returns the uses of both lookups. - Drop the namespace check: the server gives an extension no type hierarchy, and an extension type has one. - Check the removed operators value directly, and add writeTempPackage to test/support for the finder tests that write a package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
|
Are operatora talent into account in --no-public and exported-packages? |
… and test an SDK operator reached through a class that doesn't declare it The `supertypes` request keeps its result in a local, as its neighbors do: without the type argument, `_guard` infers a non-nullable list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
Every operator hierarchy gets a round of its own, so the rounds that `bool` operators shared, and the attribution of errors by their text, are gone. The call and operator lookups each restore their own unsaved changes, so the operator lookup no longer plans its rounds ahead. The hierarchy lookup asks every member for its overrides and drops the caches that the shortcut needed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
The analysis server analyzes each change about three times faster while the files are closed: a scan of `xml` 7.1.0 takes 18 s instead of 32 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
| else if (usesOf[candidate] case final uses?) | ||
| (candidate, [...refs[i], ...uses]), | ||
| ]; | ||
| await semanticTokensFor(client, usesOf.values.toList()); |
There was a problem hiding this comment.
Now semantin tokens are always fetched. Is it intentional?
There was a problem hiding this comment.
Yes. The call is unconditional now, but it fetches tokens only for the files of the uses that the two lookups found, and it skips each file whose tokens are already cached. When a lookup fails, it adds no uses, and when neither finds any, semanticTokensFor returns before it sends a request. That is what main did for the call uses: it skipped the call only when the lookup failed, and then there were no uses to fetch tokens for. The tokens are still needed, because the verdict reads the comments and annotations around each use, as it does for the references that references returns.
Generated by Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
`Location` holds the same URI and range and already compares by both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
| .new(textDocument: .new(uri: changed.toString())), | ||
| ), | ||
| ); | ||
| await hover(changed, const .new(line: 0, character: 0)); |
There was a problem hiding this comment.
Why was it changed?
There was a problem hiding this comment.
Because foldingRange didn't make the wait reliable. The server answers foldingRange from the parsed file alone, so the answer could arrive before the server reported that it had started analyzing the change. The wait then ended at once, and a round read the errors from before the rename. During this pull request, the operator tests reported used operators in some runs because of it. The server can answer hover only after it resolves the changed file, and it reports isAnalyzing: true before it starts. So once the hover answer arrives, waiting until the server is idle covers the whole analysis.
Generated by Claude Code
| /// starts a new analysis of everything that imports it. | ||
| final _changed = <String>{}; | ||
|
|
||
| /// Sends [texts], by path, and restores every other changed file. |
There was a problem hiding this comment.
What does it mean?
There was a problem hiding this comment.
show gives the server new content for some files, and it puts back the content on disk of every file that it changed before and that this call leaves out. For example, round 1 calls show({a: renamedA}), and round 2 calls show({b: renamedB}). Round 2 then sends renamedB for b and the content on disk for a, so only b's operator stays renamed. show(const {}) restores everything.
ae58b34 rewrites the doc comment to say this: "Sends [texts], the new content of files by path, to the server. A file that an earlier call changed and [texts] leaves out gets its content on disk back."
Generated by Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
| ); | ||
| } | ||
|
|
||
| /// Returns the ranges of [errors] in [path] that aren't at a declaration's |
There was a problem hiding this comment.
I don't understand. Is the method name correct? Is the comment understandable?
There was a problem hiding this comment.
Neither was good: _outsideNames said where the errors aren't, not what the method returns. It drops the errors at a declaration's name, because those come from the declaration itself, such as a class that no longer implements its interface after its operator is renamed, and they aren't uses.
Done in 9c81cb3: the method is now _rangesOfUses, documented as "Returns the ranges of [errors] in [path] that mark uses. An error at a declaration's name isn't a use: it comes from the declaration itself, such as a class that no longer implements its interface." The doc of newErrors now says only that it returns the ranges of the new errors that mark uses.
Generated by Claude Code
There was a problem hiding this comment.
Look at other methods and comments and recheck them
There was a problem hiding this comment.
Done in 2eeac44. I reread every name and comment that this pull request adds, and renamed those that didn't say what they do:
unsaved_changes.dart:show→send,newErrors→usesByPath,_errors→_publishedErrors,_changed→_changedPaths,_change→_send.operator_hierarchies.dart:_overriddenOf→_overriddenBy(the operators that a member overrides),_overriddenIn→_operatorsInSupertypes,_operatorIn→_declaredOperator,_isInside→_isInsideAnalysisRoot, andOperatorHierarchy.operators→candidates, because it holds the candidates among the members. The parameters of type_OperatorKindare now all namedkind.operator_uses.dart:_newName→_renamedKeyword,_renamed→_renamedContent.semantic_tokens.dart:lastCodeBefore→lastNonCommentBefore.
Their doc comments now match the new names. For example, OperatorHierarchy is "One override hierarchy: operators that override each other", and _publishedErrors says that warnings and hints are left out because a use of a renamed declaration is always an error. All tests pass.
Generated by Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
`abstract class Both implements Left, Right {}` inherits `+` from both, so a use of `+` through `Both` needs both, and they are renamed in one round. Before, each was renamed alone, the use compiled through the other one, and both were reported.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
|
|
||
| /// Returns the ranges of the new errors that mark uses, by path, once the | ||
| /// server has analyzed the changes. | ||
| Future<Map<String, Set<Range>>> usesByPath() async { |
There was a problem hiding this comment.
This name doesn't actually reflect what it does. It makes sense obył when news error === use from the perspective of the caller. Please give 5 proposals how we could improve that
There was a problem hiding this comment.
Agreed: the method returns the ranges of the errors that the changes added, without those at a declaration's name, and only the callers read them as uses. Five options, each paired with a name for the private helper, which has the same problem:
newErrorRanges(), with the helper_rangesNotAtDeclarationNames(path, errors). The doc says that errors at declaration names are left out, and the callers keep the nameusesByPathfor their variable.addedErrorRanges(), with the same helper. "Added" says that the errors are compared with those before the changes.newErrorRangesByPath(), with the same helper, if the key of the map should show in the name.newErrorsOutsideDeclarations(), with the helper_outsideDeclarationNames(path, errors). This names the filter, but it is long, and it says where the errors aren't rather than what is returned.- Split it:
newErrors()returns the newDiagnostics by path, and a separatewithoutDeclarationNames(errors)in each caller drops those at declaration names. The names are exact, but both callers repeat the filter call.
I'd take 1: it says what comes back, and the filter is a detail of the doc comment, as the left-out warnings are in _publishedErrors. Which one should I use?
Generated by Claude Code
There was a problem hiding this comment.
sure, shouldn't this helper _rangesNotAtDeclarationNames be reused for both implicit calls and operators? Those things are pretty similar
There was a problem hiding this comment.
Done in f2b8995: the method is now newErrorRanges(), and its helper is _rangesNotAtDeclarationNames.
It is already shared: both findImplicitCalls and findOperatorUses call changes.newErrorRanges(), and that is the only caller of the helper. On main, implicit_calls.dart had its own copy of this filter, _usesAmong. This pull request moved it into UnsavedChanges, so that both lookups use one copy.
Generated by Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5
ciach skipped operators unless
--also operatorswas set, and then it reported every operator that only other libraries used. The reference search misses those uses: it findsa + bonly in the library that declares+, it finds no relational pattern such ascase < 0, and for==it lists every use ofObject.==in the SDK. This pull request finds the uses of operators through the analysis server, reports unused operators by default and removes--also operators. It builds on the renaming that #151 added forcallmethods.How it works
operator_hierarchies.dartcollects the operators that it overrides and that override it, through every supertype and subtype of its class. An operator that a subtype inherits along with it joins too:abstract class Both implements Left, Right {}joinsLeft.+andRight.+. When one of them is outside the analysis root, such asObject.==orListBase.[], the operator is never reported: its uses can't be found, and the SDK calls==fromSetandMap.operatorkeyword of every member becomeso$$$$$$$and the name becomes spaces, so no position moves. Every new error is a use, and it counts for every member of the hierarchy, as the reference search counts a call through a subclass.a + bfails,(a + b) * 2has no error at*.calland operator lookups each restore the files that they changed, throughwithUnsavedChanges. While they change files, the open files are closed, because the server analyzes a change about three times faster then.LspClient.didChangenow opens a document that isn't open, andwaitForChangesAnalyzedwaits through ahoverrequest, which the server can answer only after it resolves the changed file. ThefoldingRangerequest that it used before could be answered before the new errors were published.--also operatorsandalso: [operators]are errors that say to drop the value, and--no-operatorssays to use// ciach:ignore. MIGRATION.md covers the change, includingFinderOptions.skipOperatorsfor library users.Cost
Each round costs one analysis of the libraries that import the renamed files:
xml7.1.0: 13-19 s before, 18 s after, with 10 fewer findings, all of them used operators such asXPathDuration.+andXmlCache.[]. Without closing the files, it took 32 s.fixnum1.1.1: 0.7 s before, 3 s after, with 6 fewer findings, such asIntX.%.Limits
dynamicgives no error, as for every member.+throughBalso counts forC.+whenCis a sibling ofB, although the use can't call it. So with--also overrides,C.+isn't reported, even when nothing calls it.Tests
test/finder/operators_test.dartscansexample/lib/scenarios/operators*.dart, which cover every operator, compound assignments, relational patterns, extensions, enums, mixins, extension types, overrides through one and two supertypes, and an override in a file that isn't scanned. Small packages written by the tests cover a three-level override chain, an SDK operator that a class overrides through an abstract class that doesn't declare it, a class that inherits+from two interfaces, and an operator whose name followsoperatoron the next line. Without renaming the whole hierarchy, the first test also reportsRound.-, which code uses only through its subclass. Without the walk through all supertypes, the SDK operator test reportsLeaf.[]andLeaf.[]=. Without joining the operators that a subtype inherits, the two-interface test reportsLeft.+andRight.+, which code uses throughBoth.test/lsp/lsp_client_test.dartchecks thatcloseAllcloses every open document.With Dart 3.13.5,
dart analyze --fatal-infos,dart formatand all tests pass. Inwebsite/,images/og.pngfails on this machine, as noted in #151.🤖 Generated with Claude Code
https://claude.ai/code/session_01RWsUwSKJvdgiVh4gAHboH5