language icon indicating copy to clipboard operation
language copied to clipboard

Dot syntax for static access

Open nate-thegrate opened this issue 5 months ago • 9 comments

Proposal

If a type T can be inferred, then interpret .foo as T.foo.

This is similar to #357, but with a broader use case: it applies to enums, static members, and also factories & constructors.


Example

// enums
Brightness brightness = .dark;
final brightness = .dark; // error: no inferred type


// static members
bool isArrowKey(LogicalKeyboardKey key) => switch (key) {
   .arrowDown || .arrowLeft || .arrowRight || .arrowUp => true, // fits on a single line!
  _ => false,
};
if (key == .arrowUp) {} // error, since == operator doesn't infer type


// factories & constructors
HSLColor favoriteColor = .fromColor(Color(0xFF00FFFF)); // factory
HSLColor favoriteHue = .fromAHSL(1, 180, 1, 0.5); // named constructor

// you don't write a dot for the unnamed constructor, so it doesn't change
const Duration twoSeconds = Duration(seconds: 2);

Here's everything put together:

@override
Widget build(BuildContext context) {
  return Container(
    width: .infinity,
    margin: .zero,
    padding: .symmetric(vertical: 8.0),
    decoration: BoxDecoration(
      color: .fromARGB(255, 0, 128, 255),
      shape: .circle,
    ),
    child: widget.child,
  );
}



Later On

Static Analysis

If/when this is implemented, it should probably come with some additional linter rules:

  • prefer class name for enums, constructors, static members
  • and the reverse: prefer no class name for enums, constructors, static members

from keyword

I really liked the keyword idea from #1955 and would love to see it implemented at some point down the line.

In that issue, from was added after the parameter name. I think that attaching it to the type would be best but would be happy either way.

abstract final class MyColors {
  static const chartreuse = Color(0xFF80FF00);
  static const spring     = Color(0xFF00FF80);
  static const vermilion  = Color(0xFFFF4000);
  static const indigo     = Color(0xFF4000FF);
}

typedef MyColor = Color from MyColors;

class AnimatedIcon extends StatelessWidget {
  const AnimatedIcon({
    required this.icon,
    required this.duration,
    required this.curve,
    required this.color,
  });
  final IconData from Icons icon;
  final Duration from Durations duration;
  final Curve from Curves curve;
  final MyColor color;
  ...
}

final icon = AnimatedIcon(
  icon: .home,
  duration: .medium1,
  curve: .easeOut,
  color: .spring,
);

nate-thegrate avatar Feb 17 '24 03:02 nate-thegrate

It's worth noting that the example from #357 wouldn't work under this proposal.

enum CompassPoint { north, south, east, west }

if (myValue == .north) {} // this should throw an error,
                          // since the other side of the == operator could have any object

But the additional keyword would make it work:

class Foo {
  bool operator ==(Object from Foo other) {
    ...
  }
}

And without a from keyword, a lack of support for == would at least be in accordance with the Flutter style guide:

Avoid using == with enum values

nate-thegrate avatar Feb 17 '24 22:02 nate-thegrate

In my opinion it brings little value, but greatly decreases code readability. You never know, where the dotted member comes from.

Consider:

SomeWidget(
  color: .blue // is that MyAppColor? CupertinoColors? Just Colors? - you never know until you hover over it.
)

AlexanderFarkas avatar Feb 20 '24 09:02 AlexanderFarkas

In my opinion it brings little value, but greatly decreases code readability. You never know, where the dotted member comes from.

Consider:

SomeWidget(
  color: .blue // is that MyAppColor? CupertinoColors? Just Colors? - you never know until you hover over it.
)

The proposal of this issue would not include this case. It would work for enums, static members, factories & constructors of a given type T that is inferred by the context.

So, if SomeWidget receives a parameter Color color, MyAppColor, CupertinoColors and Colors would never be considered for .blue. Only Color would. And as Color does not have a .blue static member, an error would be emitted.

mateusfccp avatar Feb 20 '24 12:02 mateusfccp

@mateusfccp Thank you for the explanation, I had the wrong implementations in my mind.

Yet, the issue is still there.

enum CupertinoColor {
  blue("0xXXA");
  
  const CupertinoColor(this.color);
  final String color;
}
enum MaterialColor {
  blue("0xXXB");

  const MaterialColor(this.color);
  final String color;
}
enum MyColor {
  blue("0xXXC");

  const MyColor(this.color);
  final String color;
}

// ...
SomeWidget(
  color: .blue // You don't know where this `.blue` comes from until you see SomeWidget declaration.
);

Though most of the time you don't have ambiguous declarations, I'm just bringing it up so it could be considered.

AlexanderFarkas avatar Feb 20 '24 13:02 AlexanderFarkas

Sure, you would have to know whether SomeWidget accepts MyColor, MaterialColor or CupertinoColor.

Personally, I don't see this as a huge disadvantage, and you can always use the full reference. We would probably also have linters to enforce the full or dot syntax, depending on the preference of the team.

mateusfccp avatar Feb 20 '24 13:02 mateusfccp

I have added somewhat larger-in-scope design proposal.

It has some ideas for how to allow more constants than just on the type itself, but it still cannot reach Flutter's Colors class. I don't think any reasonable design can.

lrhn avatar Apr 05 '24 13:04 lrhn