language
language copied to clipboard
Dot syntax for static access
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 enum
s, 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,
);
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
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.
)
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 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.
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.
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.
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).
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.
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.