dart wrongly assumes value can be nullable

Solution 1:

Map.operator [] returns a nullable type, period. The compiler doesn't know what WORDLIST[language] will return at runtime; it doesn't know that WORDLIST isn't some Map implementation that returns different values each time operator [] is called. This is the same reason why only local variables can be type promoted.

The typical ways to fix this are:

  • Use ! to force the result to be non-nullable:
    if (WORDLIST[language]==null) throw new Exception("Invalid Language");
    List<String> wordlist = WORDLIST[language]!;
    
  • Use a local variable:
    var result = WORDLIST[language];
    if (result == null) throw new Exception("Invalid Language");
    List<String> wordlist = result;
    
    Since wordlist is probably already a local variable, you also could just reorder your code:
    List<String>? wordlist = result;
    if (wordlist == null) throw new Exception("Invalid Language");
    // wordlist is now type-promoted to be List<String>.
    

Solution 2:

The compiler is not smart enough to promote general expressions, only local variable can be promoted.

When you write if (WORDLIST[language] == null) throw ..., that doesn't actually make the compiler any wiser wrt. the following WORDLIST[language] expression. It takes specific knowledge about the implementation of the [] operator of WORDLIST to guarantee that it returns the same value every time it's called with the same argument. The compiler does not assume to have such knowledge.

What you should do is:

List<String>? wordlist = WORDLIST[language];
if (wordlist == null) {
  throw ArgumentError.value(language, "language", "Invalid language");
}
// use `wordlist` as `List<String>` here.

By assigning the value to a local variable, you allow the test+throw to promote that variable to non-nullable in the following (implicit else-branch) code.