dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.28k stars 1.58k forks source link

Create an issue #57024

Closed tolotrasamuel closed 2 weeks ago

tolotrasamuel commented 2 weeks ago

Dart infers type incorrectly with inheritance.

Dart type inference is not working properly with inheritance and field overriding.

If we don't explicitly write final Dog pet = Dog(); (repetitive) the analyzer thinks that pet is Animal. (which is not wrong but problematic, it should be Dog)

class Animal {}

class Dog extends Animal {
  void bark() {
    print("Daawg!");
  }
}

abstract class Owner {
  Animal get pet;
}

class DogOwner extends Owner {
  @override
  final pet = Dog();
}

void main() {
  final dog = Dog();
  dog.bark(); // pass

  final owner = DogOwner();
  owner.pet.bark();  // error. Why ??
}

However, DogOwner.pet is clearly of type Dog (at least for the human reader)

Is this a bug in Dart or intended ? I am curious to know why would this be intended.

Dart version 3.4.3

dart-github-bot commented 2 weeks ago

Summary: The issue reports that Dart's type inference incorrectly infers the type of Foo.baz as Baz instead of Boz when Boz extends Baz and Foo overrides Bar.baz with a Boz instance. The user is unsure if this is intended behavior or a bug.

eernstg commented 2 weeks ago

You need to declare that the type of the getter in DogOwner is Dog:

class DogOwner extends Owner {
  @override
  final Dog pet = Dog();
}

When you don't declare the return type of a getter (in this case: the implicitly induced getter of the instance variable DogOwner.pet), the step which is called override inference prevails over the kind of inference that provides the type of a variable based on its initializing expression. So DogOwner.pet has the type Animal rather than Dog because it overrides Owner.pet which has type Animal.

This is a language design choice. It would certainly have been possible to give the initializing expression the higher priority.

However, with this design choice we give priority to the public interface of the class by using the inherited return type, (which is definitely part of the public interface of the superclass) and not the type of the initializing expression (which is an implementation detail). I think this is a useful design choice.

In any case, it's working as intended. So I'll close the issue.