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

Dot syntax for static access #3616

Open
nate-thegrate opened this issue Feb 17, 2024 · 9 comments
Open

Dot syntax for static access #3616

nate-thegrate opened this issue Feb 17, 2024 · 9 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@nate-thegrate
Copy link

nate-thegrate commented Feb 17, 2024

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 nate-thegrate added the feature Proposed language feature that solves one or more problems label Feb 17, 2024
@nate-thegrate
Copy link
Author

nate-thegrate commented Feb 17, 2024

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

@AlexanderFarkas
Copy link

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.
)

@mateusfccp
Copy link
Contributor

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.

@AlexanderFarkas
Copy link

@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.

@mateusfccp
Copy link
Contributor

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.

@lrhn
Copy link
Member

lrhn commented Apr 5, 2024

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.

@tatumizer
Copy link

This^ design proposal is similar to swift's implicit member expressions, except that for "other operators", swift allows only a chain of postfix operators, which always starts with the .id member expression (see examples in the linked doc).

@lrhn
Copy link
Member

lrhn commented May 27, 2024

The postfix operator allows operations like .values.first or .values.byName[json["name"] as String] for enums. It's not unreasonable, and really means that .selectors means contextType.selectors.

I'd probably not allow looking in subclasses or related classes if we also allow postfix expressions. That would open for too many combinations, and increase the risk of conflicts.

@tatumizer
Copy link

tatumizer commented May 27, 2024

I think this kind of treatment simplifies design and simultaneously expands the scope of the feature.

Suppose Foo is a context type. When the user, say, types Foo foo = ., IDE suggests ALL static methods defined in class Foo (not in subclasses certainly), and then it's the user's responsibility to ensure that the whole chain of postfix expressions that starts with selected .id is consistent with the type Foo (its type can be a subclass of Foo, but it's nothing new).

Here's the benefit: class Colors, instead of being declared as a class, may become static extension Colors on Color, preserving backward compatibility (almost -see *), and then we kill two birds with one stone: whatever was available via Colors.red is still available, plus - because all static constants defined in this extension are subclasses of dart:ui Color -they become available for shortcuts. In other words, now we have two possible prefixes for color red: backward-compatible Colors.red uses the extension name, and Color.red from static extension on Color, which makes the shortcut applicable to any parameter declared with the type Color.

(*) currently, class Colors defines a constructor Colors(), and a small number of instance methods like toString (inherited from Object), which cannot
be ported to a static extension. I wonder if anyone really uses Colors() as a constructor (not clear what purpose it can serve).

Edit: some commenters on this and other designs are concerned about readability. Making a program readable is the responsibility of the user, not of the language. There're many ways to make the program unreadable with or without this feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

5 participants