Skip to content

common developer bad coding style issue of nullable list iteration using for-loop #59567

@raymondmakz

Description

@raymondmakz

Hi everyone,
I am have encounter a problem about a common coding style which potential trigger runtime exception.
But I cannot really tell it is related to dart-core or linter issue.

My dart version is 3.5.1
I have read the 3.7.x lint rule and test case file for omit_obvious_local_variable_types and specify_nonobvious_local_variable_types.
Ref: #58773

Here is the code:

class Fruit
{
  final String name;
  const Fruit(String name) : this.name = name;
  
  @override
  String toString(){
    return '(Fruit name: $name)';
  }
}

List<Fruit> getFruitList(){
  return <Fruit>[Fruit('pear'), Fruit('apple')];
}

List<Fruit>? getNullableFruitList(){
  return <Fruit>[Fruit('pear'), Fruit('apple')];
}

Map<String, Fruit>? getNullableFruitMap(){
  var map = <String, Fruit>{
    'PEAR': Fruit('pear'), 
    'APPLE': Fruit('apple'),
  };
  return  map;
}

void main(){

  var fruitList = getFruitList();
  var nullableFruitList = getNullableFruitList();
  var nullableFruitMap = getNullableFruitMap();

  //Case 1
  for (var fruit in fruitList ?? []){      //$fruit inferenced as dynamic, even fruitList is not null
    print(fruit.name);
    print(fruit.non_exist_prop);     //NO LINT, But Unhandled exception: NoSuchMethodError: Class 'Fruit' has no instance getter 'non_exist_prop'.
  }

  //Case 2
  for (var fruit in nullableFruitList ?? <Fruit>[]){   //use implict typed list, $fruit inferenced as Fruit
    print(fruit.name);
    print(fruit.non_exist_prop);     //LINT, undefined_getter
  }

  //Case 3
  for (var fruit in nullableFruitList ?? []){      //$fruit is inferenced as dynamic
    print(fruit.name);
    print(fruit.non_exist_prop);   //NO LINT, But Unhandled exception: NoSuchMethodError: Class 'Fruit' has no instance getter 'non_exist_prop'.
  }

  //Case 4
  for (var fruit in nullableFruitList ?? []){      //$fruit is still inferenced as dynamic
    print((fruit as Fruit).name);
  }

  //Case 5
  for (Fruit fruit in nullableFruitList ?? []){
    print(fruit.name);
    print(fruit.non_exist_prop);   //LINT, undefined_getter
  }

  //Case 6
  var nonNullableFruitList = nullableFruitList ?? [];   //$nonNullableFruitList is inferenced as List<Fruit>
  for (var fruit in nonNullableFruitList){      //$fruit is inferenced as dynamic
    print(fruit.name);
    print(fruit.non_exist_prop);   //LINT, undefined_getter
  }

  //Case 7
  (nullableFruitList ?? []).forEach((f) {
    print(f.name);
    print(f.non_exist_prop);   //LINT, undefined_getter
  });

  for (var mapEn in nullableFruitMap?.entries ?? {}){}   //$mapEn is inferenced as Object? , because {} is Set<dynamic> 
  for (var mapEn in nullableFruitMap?.entries ?? []){}   //$mapEn is inferenced as Object? , because {} is List<dynamic>
  for (var mapEn in (nullableFruitMap ?? <String, Fruit>{}).entries){}   //$mapEn is inferenced as MapEntry<String, Fruit>

}

Look at case 1, case 3 and case 6.
It seems that the for-loop declaration have its own type inference on null-coalesce logic.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3A lower priority bug or feature requestarea-devexpFor issues related to the analysis server, IDE support, linter, `dart fix`, and diagnostic messages.devexp-linterIssues with the analyzer's support for the linter package

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions