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

Is "Never" is an object at runtime? #52961

Open
matanlurey opened this issue Jul 17, 2023 · 5 comments
Open

Is "Never" is an object at runtime? #52961

matanlurey opened this issue Jul 17, 2023 · 5 comments
Assignees
Labels
area-front-end Use area-front-end for front end / CFE / kernel format related issues. P2 A bug or feature request we're likely to work on

Comments

@matanlurey
Copy link
Contributor

matanlurey commented Jul 17, 2023

No idea if this is expected (i.e. in the spec) or not, but it definitely confused me on #52962:

void main() {
  // error: Instance member 'hashCode' can't be accessed using static access.
  print(String.hashCode);
}
void main() {
  // error: Instance member 'hashCode' can't be accessed using static access.
  print(Null.hashCode);
}
void main() {
  // error: 79408728 on Dart2JS at the particular moment I ran this in DartPad
  print(Never.hashCode);
}

Both analyzer and compilers seem to accept this as valid.

@rakudrama
Copy link
Member

Never is an object, but it is Type object.

Unlike Record (and Function), which are interface-type supertypes of records (functions) and might reasonably have members (like Function.apply), Never is not an interface type so has no members.
So Never.hashCode is the same as (Never).hashCode.

Typedefs of other types that do not have members behave like Never.
So you might imagine that Never is just typedef for some internal thing.
It does seem inconsistent with FutureOr, which also is not an interface type, and void, which is a keyword.

typedef RecordIntInt = (int, int);
typedef Void = void;

void main() {
  print(Never.hashCode);
  print((Never).hashCode);
  print(Never.runtimeType);
//print(String.runtimeType);       // compile-type error - semantic error.
  print(RecordIntInt.runtimeType);
//print(void.runtimeType);         // compile-time error - parse error.
  print(Void.runtimeType);
}

60699869
60699869
_Type
_Type
_Type

This behaviour, where what I can do with a typedef depends on how the typedef is defined, does make typedef less useful for defining my library. I can't switch between

typedef Handle = int;

and

typedef Handle  = (int, int);

without possibly breaking a client.

@eernstg
Copy link
Member

eernstg commented Jul 18, 2023

We should actually have a compile-time error for Never.hashCode because of this rule:

It is a compile-time error to invoke an instance method on a type literal that is immediately followed by the token `.' (a period).

The error messages for String.hashCode and for Null.hashCode do follow this rule, but, apparently, it is not detected that Never is a type literal.

@lrhn
Copy link
Member

lrhn commented Jul 18, 2023

Agree. There is no distinction between interface types and other types here, the only real difference is likely that the keyword void is not a valid expression by itself.

A typeLiteral.identifier should always be interpreted as an attempt to access a static member (or constructor, so an access on the static score), and it's an error of that type/scope does not have that static member.
Including if the type cannot have any static members.

(I'm not sure we treat an instantiated type literal member access like List<int>.foo as a static lookup, but we should.)

@a-siva a-siva added the area-front-end Use area-front-end for front end / CFE / kernel format related issues. label Jul 18, 2023
@johnniwinther johnniwinther added the P2 A bug or feature request we're likely to work on label Jul 19, 2023
@chloestefantsova
Copy link
Contributor

chloestefantsova commented Jul 24, 2023

Apparently, there's code in the wild that has expressions like T.toString() where T is a type variable. For example, the following Flutter code: https://github.com/flutter/flutter/blob/9c10151508bdb8ddd9d7b56484373d23755d8431/packages/flutter/test/rendering/mock_canvas.dart#L1009

@eernstg, should we initiate the breaking change process to forbid the access to members on type literals?

@eernstg
Copy link
Member

eernstg commented Jul 24, 2023

Yes, I think it would be a good idea to treat it as a breaking change. I think the alternative (just make it a non-error) is too inconsistent to be helpful (exactly which type literals will allow this, and which ones won't?). Also, the fix is easy because the expression T.toString() can just be turned into (T).toString().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-front-end Use area-front-end for front end / CFE / kernel format related issues. P2 A bug or feature request we're likely to work on
Projects
None yet
Development

No branches or pull requests

7 participants