Skip to content

Can a constructor named runtimeType or hashCode be torn off? #1718

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

Closed
srawlins opened this issue Jul 1, 2021 · 8 comments
Closed

Can a constructor named runtimeType or hashCode be torn off? #1718

srawlins opened this issue Jul 1, 2021 · 8 comments

Comments

@srawlins
Copy link
Member

srawlins commented Jul 1, 2021

Forgive my bare understanding of the parsing rules, but I was surprised to see co19 language tests like named_constructor_A03_t02 which include code like:

var v3 = (C<dynamic>).constr;

I had thought that C<dynamic> when not followed immediately by a . would be a Type, which does not have a getter called constr. In any case, assuming this is a valid test, I wonder then, given a class with constructors runtimeType or hashCode, are the following variables constructor tearoffs or field accesses on Type?

class C<T> {
  C.runtimeType();
  C.hashCode();
}

void main() {
  (C).runtimeType; // In current Dart, this accesses `runtimeType` on the Type.
  (C<int>).runtimeType; // Illegal in current Dart.
}

In other words, do constructors shadow instance getters that exist on Type, or vice versa?

I think the question can extend to a constructor named toString. (C).toString normally represents the tearoff of the toString function on Type, but what if C has a constructor named toString?

@lrhn
Copy link
Member

lrhn commented Jul 1, 2021

For non-generic types, the answer is simply "yes", they can be torn off.

Also, I think (C<int>).constr should be a compile-time error.

The current behavior for C.runtimeType is to say that C doesn't have a static member named runtimeType. You already need to write (C).runtimeType to get the Type object for Type (Or just write Type, since you know what the result will be).
With constructor tear-offs, C.runtimeType, which is currently invalid, would become a valid constructor tear-off, and (C).runtimeType keeps being an instance getter on the Type object for C.

For C<int>.runtimeType, which is currently invalid, we can consider our options.
You cannot access static members as C<int>.staticOnC (we could allow it, but it looks deceptively like the static members could use the type variable, and it's not necessary). That opens us up to allowing C<int>.instanceOnType, but we don't actually need that (there aren't that many interesting instance members on Type) and it's inconsistent with the non-generic case, so I'd go with disallowing it, and only allowing C<int>.something when something is a constructor. You'll still need (C<int>).toString() to get "C" (again, you know the result, so it's a useless expression to give preference to).

And (typeLiteral).instanceMemberOnType should still give you access to instance members on Type, nothing more. Static/constructor accesses need to be directly on the class, not parenthesized.

@srawlins
Copy link
Member Author

srawlins commented Jul 1, 2021

Thanks!

Also, I think (C<int>).constr should be a compile-time error.

Then tests like named_constructor_A03_t02 which use it need to be fixed? Or is that notion just held in the test status database?

And (typeLiteral).instanceMemberOnType should still give you access to instance members on Type, nothing more. Static/constructor accesses need to be directly on the class, not parenthesized.

Perfect. The ambiguity I was worried about vanishes if (C<int>).constr is a compile-time error.

@eernstg
Copy link
Member

eernstg commented Jul 1, 2021

tests like named_constructor_A03_t02 which use it need to be fixed?

Yes, that one should expect the compile-time error that Type has no member named constr at lines 40, 44, 48, or it should stop using those () such that the test can be executed.

@srawlins
Copy link
Member Author

srawlins commented Jul 7, 2021

@lrhn wrote:

Also, I think (C<int>).constr should be a compile-time error.

I think the spec contains two examples of that syntax:

var v5 = (C.name)<typeArgs>(args);

You can write (List.filled)<int>(4, 4) to do the tear-off

@lrhn
Copy link
Member

lrhn commented Jul 7, 2021

That's slightly different syntax.

You can do the tear-off (which is what the parentheses ensure) then invoke the resulting generic function.
You cannot instantiate a function value, nor can you tear-off constructors from a Type object, which is why
(C.named)<int> and (C<int>).named are both invalid (the former only if not followed by (...), because then it's a call, not an instantiation).

Assume we have a generic class C<T> with an unnamed constructor C(); and one named C.name();, then

expression valid? meaning
C Type literal for C<dynamic>
C<int> Type literal for C<int>
(C)<int> Cannot instantiate a type literal
C.named
C.new
Generic constructor tear-off
(C).named No named member on Type
(C).new Not even valid syntax, only new after a class
C<int>.named
C<int>.new
Instantiated constructor tear-off
(C<int>).named No named member on Type
(C<int>).new Still not even valid syntax
C.named<int>
C.new<int>
The constructor is not generic.
(C.named)<int>
(C.new)<int>
Cannot instantiate a function value
C() Unnamed constructor invocation, implicit type argument
(C)() Type object isn't callable
C.named()
C.new()
Generic constructor invocation with implicit type arguments
(C.named)()
(C.new)()
Generic constructor tear-off, invoked with implicit type arguments
(C).named()
(C).new()
Not valid on a Type object
C<int>() Unnamed constructor invocation, explicit type argument
(C<int>)() Cannot call a Type object
(C)<int>() Cannot call a Type object
C<int>.named()
C<int>.new()
Constructor invocation, explicit type argument
(C<int>.named)()
(C<int>.new)()
Instantiated constructor tear-off, invoked
(C<int>).named()
(C<int>).new()
Not valid on a Type object
(C)<int>.named()
(C)<int>.new()
Not valid on a Type object

In general, you can only put <int> after a reference to a declaration (or instance method) unless it's followed by (...), then it's a generic invocation. Anything parenthesized is a value, not a declaration.

@srawlins
Copy link
Member Author

srawlins commented Jul 8, 2021

Oops, my mistake is obvious, haha. Thanks for the great explanation, @lrhn !

@srawlins
Copy link
Member Author

This can probably be closed, I think it's all well-defined.

My one caveat would be extension methods. I sort of want an explicit note (or test case) that the following results in an error:

class C<T> {}

extension on Type {
  int get ex => 1;
}

var a = C<int>.ex;

But I can read that as "currently covered" in the "Constructor/type object member ambiguity" section.

@eernstg
Copy link
Member

eernstg commented Aug 27, 2021

Note that the error 'Cannot instantiate a function value' here is likely to go away, cf. #1812.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants