dart-lang / language

Design of the Dart language
Other
2.66k stars 205 forks source link

Dart's difficulty to analyze code flows within a method that needs to return a specific object. #2121

Open SEAFromJahu opened 2 years ago

SEAFromJahu commented 2 years ago

Include a permanent link to the error if possible.

https://help.github.com/en/articles/getting-permanent-links-to-files

**In the attached code, Dart has enough information to correctly define which type of object (animal/Cat or animal/Dog) is being passed to the function, but even so the compiler believes it needs a 'return' outside the if/else structure if to avoid terminating without returning any value...**

main() {
  Cachorro cachorro1 = Cachorro("Rex", 3);
  cachorro1.comer();
  cachorro1.dormir();
  cachorro1.latir();

  print(cachorro1.toString());

  Gato gato1 = Gato("Tom", 5);
  gato1.vidas--;
  gato1.comer();
  gato1.dormir();
  gato1.miar();

  print(gato1.toString());

  List<Animal> animais = [];

  //animais.add(gato1);
  animais.add(cachorro1);
  animais.add(gato1);
  animais.add(cachorro1);
  animais.add(gato1);
  animais.add(cachorro1);

  print("-----------------------------");
  print("-----------------------------");
  Animal? animal1 = funcao(animais.first);
  print("-----------------------------");
  print("-----------------------------");
  if (animal1 is Cachorro) {
    animal1.latir();
    print("O cachorro já latiu...");
  } else if (animal1 is Gato) {
    animal1.miar();
    print("O gato já miou...");
  }
}

// ????????????????????????????????????????????
// Returns Cat/Dog or null...
// Dart does not allow deleting from this method "?" and "return null;" without signaling warning.
// ????????????????????????????????????????????
Animal? funcao(Animal toto) {
  print("Trace:...Iniciando a Função");
  //Animal animal1 = toto;
  if (toto is Cachorro) {
    print("O cachorro vai latir...");
    toto.latir();
    print("Trace:...Recebeu um Cachorro -> Retorna o Cachorro!");
    return Cachorro(toto.nome, toto.idade);
  } else if (toto is Gato) {
    print("O gato vai miar...");
    toto.miar();
    print("Trace:...Recebeu um Gato -> Retorna o Gato!");
    return Gato(toto.nome, toto.idade);
  }
  return null;
}

abstract class Animal {
  Animal(this.nome, this.idade);

  String nome;
  int idade;

  void comer() {
    print("Comeu");
  }

  void dormir() {
    print("Dormiu");
  }

  @override
  String toString() {
    return "Animal -> Nome: $nome Idade: $idade";
  }
}

class Cachorro extends Animal {
  Cachorro(String nome, int idade) : super(nome, idade) {
    print("Criou o cachorro $nome!");
  }

  void latir() {
    print("Au au");
  }

  @override
  void dormir() {
    super.dormir();
    print("Roncando muito!!!");
  }

  @override
  String toString() {
    return "Cachorro -> Nome: $nome Idade: $idade";
  }
}

class Gato extends Animal {
  Gato(String nome, int idade) : super(nome, idade) {
    print("Criou o gato $nome!");
  }

  int vidas = 7;
  void miar() {
    print("Miauuu");
  }

  @override
  String toString() {
    return "Gato -> Nome: $nome Idade: $idade";
  }
}
lrhn commented 2 years ago

The Dart compiler does not assume that there are no further classes implementing Animal, so it doesn't know that there is no chance of reaching the return null;. (Even if it did know that, the flow analysis wouldn't be smart enough to use it.)

The problem with Animal funcao(Animal toto) { ... } is that you need to return an Animal on every path.

Returning null and returning nothing (which effectively returns null) are not options if the return type is not nullable. You have to do something else.

What you can do instead is to throw as the third option:

Animal funcao(Animal toto) {
  if (toto is Cachorro) {
    // ...
    return Cachorro(toto.nome, toto.idade);
  } else if (toto is Gato) {
    // ...
    return Gato(toto.nome, toto.idade);
  }
  throw ArgumentError.value(toto, "toto", "Unknown animal");
  // or: throw UnsupportedError("Unknown animal: $toto");
}

If the only two animals your program contains are Cochorro and Gato, then the code will never reach the throw, but it being there will make the compiler happy by not trying to return null.

Another alternative would be:

Animal funcao(Animal toto) {
  if (toto is Cachorro) {
    // ...
    return Cachorro(toto.nome, toto.idade);
  } 
  Gato gato = toto as Gato;
  // ...
  return Gato(gato.nome, gato.idade);
}

It's basically the same, you just get an error at the cast if toto is neither Cochorro or Gato.