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

Inconsistencies around the interface of Null. #2759

Open
modulovalue opened this issue Jan 6, 2023 · 2 comments
Open

Inconsistencies around the interface of Null. #2759

modulovalue opened this issue Jan 6, 2023 · 2 comments

Comments

@modulovalue
Copy link

Consider the following:

void main() {
  final Null a = null;
  print(a == a);
}

This program compiles and prints true. However, If we look into the API of Null (go to declaration), it does not contain a == member and its comment says:

The `Null` class is the only class which does not implement `Object`.

The subtyping rules document confirm the validity of that comment i.e. quote: The type Object is the super type of all concrete types except Null.

I'm assuming that ==, runtimeType and noSuchMethod exists on 'values' because they are subtypes of Object, so this seems odd to me.

Also, dartdoc says that these members have been inherited (from Object?).

I think a clean solution would be to add the missing members to the interface of Null, and perhaps to have an empty not-quite-top type e.g. #2756 to denote that Null is not an Object?

@eernstg
Copy link
Member

eernstg commented Jan 6, 2023

The current language specification says that Null extends Object and doesn't declare any additional members. Obviously, this is one of the parts of the specification that changes substantially with null safety, so we need to look there.

The language specification under review, with null safety updates say that Null is a subtype of every top type and a subtype of every type of the form T? (and one more case for FutureOr), but it does not specify any superclass. However, it says the following:

The Null class declares exactly the same members with the same signatures as the class Object.

I'd prefer to have a plain, named top type, with the same semantics as Object?, but denoted by a name (rather than a non-atomic type of the form T?). Let's call it Any. We could then say that both Null and Object are subclasses of Any, and none of them add any members to the signature of Any (they can override the implementation, and we wouldn't know).

However, the current description, in the version with null safety, yields the same results. In particular, null does have an operator == (and hashCode, etc), and == takes an Object and returns a bool (note that e1 == e2 has a semantics where operator == is never invoked with an argument whose value is null, so it doesn't have to take an Object?).

This is also consistent with

The Null class is the only class which does not implement Object.

However, the dartdoc entry should not imply that anything in Null is inherited from Object.

@lrhn, that sounds like a somewhat gnarly problem, because we can't say that the members of Null have been inherited from Object?. It looks like the nice solution would be to introduce that Any class (explicitly), and then say that both Object and Null have the same members in their interface because they're obtained from Any (which could be a superclass of both, or whatever will provide consistency with all other parts).

@mit-mit mit-mit transferred this issue from dart-lang/sdk Jan 6, 2023
@lrhn
Copy link
Member

lrhn commented Jan 6, 2023

We have type hierarchy with an infinity of top types (FutureOrn(Object?), etc). Adding one more, which is a mutual sub-type of every other top type, probably won't change anything.
We can't make Any just a plain abstract, unimplementable class, which is a supertype of Object and Null, because then Object? should be a proper subtype of Any (union types are subtypes of every shared supertype of the two union members, and a supertype of both union members, and with transitivity, that's all the type relations of a union type, and by that it would be a supertype of Object?). We can probably just keep special casing Object? by declaring it a supertype of all types, then it'll be a supertype of Any too, and we have a mutual subtype relation. (That's how we collapse all top types - if anything would be a supertype of Object?, it's defined to also be a subtype, so everything above Object? collapses into one equivalence class.)

I'd probably try to define the Any type by saying that there exists an anonymous type with an interface containing the five "Object members", and that both Object and Null implement this unnamable type. I just wouldn't give it a name that anyone can refer to, an I'd make sure that it won't be introduced by any kind of type LUB or inference (that'll always end up with Object? instead). So an "Any" type, but without making the name available to anyone, it's entirely a specification device. Nothing useful comes from giving it a name, not unless we want to start having values which does not have that type too.

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

No branches or pull requests

3 participants