Allow passing type arguments to constructors on dot-shorthand access
I was trying the new dot-shorthands feature.
My idea was to create an annotation and accept different types in a list (see https://github.com/dart-lang/test/issues/2558#issuecomment-3499324747 for more details). My final idea to avoid having to add typedefs everywhere I wanted a nullable type or structural type was to create a placeholder class that would accept one type parameter.
@annotation(types: [TypeHolder<int?>(), TypeHolder<void Function()>()])
But this is a really big name, and I thought of using .new (or even creating a .t constructor for it). But that would not work for my case. Since then, I can't pass the type arguments; they must be added before the ..
This problem is also somewhat related to:
- https://github.com/dart-lang/sdk/issues/61901
I'm proposing that for constructors in dot-shorthand access, since they can't have their own arguments, we allow passing the type arguments before the ( as all other invocations.
So the code above could become:
@annotation(types: [.t<int?>(), .t<void Function()>()])
And yes, having the constraints on the list of types is good because this way we ensure the passed value can always have one (and only one) type argument.
CC @srawlins @lrhn @eernstg
The way dot-shorthands work is based on the context type. If you can pass type arguments, then it's boo longer using the context type, but (presumably) another installation of the same generic type. That's quite different. It's also syntactically similar to calling a generic static method. It doesn't look like a constructor call.
If this wasn't constant, is suggest making it a static function.
Here I'd recommend just making a typedef t<T> = TypeHolder<T>;. That's even shorter than the dot shorthand.
Also, putting type arguments to the class after the constructor name would get in the way of having generic constructors in the future.
The way dot-shorthands work is based on the context type. If you can pass type arguments, then it's boo longer using the context type, but (presumably) another installation of the same generic type. That's quite different.
About using the context type: for the majority of cases, it still would; it would simply add an option to devs to be more specific when instantiating their classes.
Here I'd recommend just making a
typedef t<T> = TypeHolder<T>;. That's even shorter than the dot shorthand.
Yes, it can work, but in that case, I'd have to think if simply creating the class with the t name isn't simpler. The idea is that the class name would explain to the user what it does, and they would be able to use dot shorthand to make use of it in a simpler way.
It's also syntactically similar to calling a generic static method. It doesn't look like a constructor call.
[...]
Also, putting type arguments to the class after the constructor name would get in the way of having generic constructors in the future.
This could be solved by simply changing the syntax a bit and allowing .<type>name(), although we are not used to it, it may be useful, and people would probably get the main idea.
I think <typeArguments>.name ... could work. This would allow the dot shorthand to remain a term where we're just omitting the name of the declaration whose static namespace we are searching.
This syntax could work technically (that is, for the parser etc.), but it might still be considered hard to read.
@annotation(types: [<int?>.new(), <void Function()>.new()])
I can see that there are places where a Foo<Object?> is asked for, but you want to provide a Foo<Something>() instead, or multiple inside a collection like here.
I'm not sure it's that common. It'd mostly be for cases where the type parameter is itself part of the data, where the use or behavior of the object depends on differences between type parameters. That is, where the values are not used generically as Foo<Object?> values, because then it wouldn't matter if they were Foo<Object?> instances.
And if it didn't have to be a constat context, you could just use a static function.
class TypeHolder<T> {
static TypeHolder<S> t<S>() => TypeHolder<S>();
}
So not sure it's a common enough use-case to warrant a somewhat complicated syntactic feature.
If I'm wrong, and we get more requests for this, then <Foo>.foo(args) would be most consistent with current syntax, but completely inconsistent with the dot-shorthand convention that the . comes first. (Well, unless it's after const.)
You can always write the type. If that is too long, maybe it's because you are designing a DSL, not writing Dart. There are limits to how far Dart syntax should stretch itself just to allow people to not write Dart. (Not that I wouldn't blatantly abuse any chance to write a DSL inside my language. Maybe that's why I shouldn't be able to.)