Skip to content

Conversation

@christopherfujino
Copy link
Contributor

@christopherfujino christopherfujino commented Mar 1, 2022

Migrating this file earlier would have caught #98284

Part of #71511

@flutter-dashboard flutter-dashboard bot added the tool Affects the "flutter" command-line tool. See also t: labels. label Mar 1, 2022
@christopherfujino christopherfujino marked this pull request as ready for review March 2, 2022 18:11
@flutter-dashboard
Copy link

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

@Hixie
Copy link
Contributor

Hixie commented Mar 2, 2022

test-exempt: code refactor with no semantic change

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the null should be handled if the download fails and this returns null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kind of weird this doesn't just pass in the checksumDependencies map, but the null migration looks fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure packageName! is safe here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. it works
  2. it seems to me like a bug we should fix if we don't know the packageName by this point

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the null bool meaningful, or can this be

Suggested change
bool? isDevDependency; // Whether this dependency is under the `dev dependencies` section.
bool isDevDependency = false; // Whether this dependency is under the `dev dependencies` section.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null means we haven't set it yet. This shouldn't be read before it's set, so I think late would make more sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made late

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've come around to @jonahwilliams's way of thinking about late (not late final): with few exceptions it should be avoided, except in tests. It usually points to some state needing to be known before the object is initialized, but we're setting it later, leaving the object in an invalid state. For example,

          final PubspecDependency? dependency = PubspecDependency.parse(line, filename: filename);
          if (dependency != null) { // We got one!
            // Track whether or not this a dev dependency.
            dependency.isDevDependency = seenDev;

could instead be:

          final PubspecDependency? dependency = PubspecDependency.parse(line, filename: filename, isDevDependency: seenDev);
          if (dependency != null) { // We got one!
class PubspecDependency extends PubspecLine {
  PubspecDependency(
    String line,
    this.name,
    this.suffix, {
    required this.isTransitive,
    required DependencyKind kind,
    required this.version,
    required this.sourcePath,
    required this.isDevDependency,
  }) : _kind = kind,
       super(line);

  static PubspecDependency? parse(String line, { required String filename, required bool isDevDependency }) {
...
    return PubspecDependency(line, package, suffix, isTransitive: isTransitive, version: version, kind: stripped.isEmpty ? DependencyKind.unknown : DependencyKind.normal, sourcePath: filename, isDevDependency: isDevDependency);

...
  final bool isDevDependency;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've come around to @jonahwilliams's way of thinking about late (not late final): with few exceptions it should be avoided, except in tests. It usually points to some state needing to be known before the object is initialized, but we're setting it later, leaving the object in an invalid state. For example,

          final PubspecDependency? dependency = PubspecDependency.parse(line, filename: filename);
          if (dependency != null) { // We got one!
            // Track whether or not this a dev dependency.
            dependency.isDevDependency = seenDev;

could instead be:

          final PubspecDependency? dependency = PubspecDependency.parse(line, filename: filename, isDevDependency: seenDev);
          if (dependency != null) { // We got one!
class PubspecDependency extends PubspecLine {
  PubspecDependency(
    String line,
    this.name,
    this.suffix, {
    required this.isTransitive,
    required DependencyKind kind,
    required this.version,
    required this.sourcePath,
    required this.isDevDependency,
  }) : _kind = kind,
       super(line);

  static PubspecDependency? parse(String line, { required String filename, required bool isDevDependency }) {
...
    return PubspecDependency(line, package, suffix, isTransitive: isTransitive, version: version, kind: stripped.isEmpty ? DependencyKind.unknown : DependencyKind.normal, sourcePath: filename, isDevDependency: isDevDependency);

...
  final bool isDevDependency;

I agree with you that it is a code smell and that doing the parsing in a factory would be better. I would prefer to do refactors in a separate PR, lest I break something in them and it gets reverted. How about if I file a tracking issue to clean up this file?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Or don't and we can just fix it if it someday breaks. This is an internal-ish development feature we don't need to spend too much time on it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also the isDevDependency one could be done without much refactoring by just adding it to the constructor as in my code snippet. _lockIsOverride is trickier.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be instantiated to avoid the late?

Suggested change
late bool _lockIsOverride;
bool _lockIsOverride = false;

Copy link
Contributor Author

@christopherfujino christopherfujino Mar 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by making it late, we ensure that it has been initialized before reading it. Is there a valid reason to read this value before we've parsed the lockfile in parseLock()?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is trickier since (without a bigger refactor) it does seem to need to be set after initialization. "Not yet set" could be a nullable boolean, where null means it hasn't yet been parsed. It seems loaded to have a lockIsOverride interface that the consumer of the API needs to understand can only be called after another method? And that, for example describeForFakePubspec calls it, so you need to also never call that method unless you've called parseLock first? It's a confusing contract.
If I were to build it from scratch I might have parseLock not mutate itself, but instead produce a new object that has what we need, and properties for lockLine and lockIsOverride. But I haven't looked too closely at this.

However I get you want to minimize the refactoring, so follow your heart. In either case, it seems like _lockLine and _lockIsOverride should either both be nullable, or both be late.

@christopherfujino
Copy link
Contributor Author

Filed #99928 to track refactoring to remove late and nullable, uninitialized fields.

Copy link
Contributor

@Jasguerrero Jasguerrero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 17, 2022
@christopherfujino christopherfujino deleted the null-safe-update-packages branch March 17, 2022 22:58
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Mar 18, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Mar 18, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Mar 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants