Type inference breaks when using records?

The following code is a simple example of a generically typed class which takes a value and a callback that accepts the value as an argument, and then a version which uses a Record that contains the value.

TestNoRecord works like you would expect, and outputs 3.

TestRecord fails at compile time on this line:

final test = TestRecord(('foo',), (val) => print(val.length));

because it infers a type of Object? for val in the function argument.

TestRecordWorkaround works as expected again, outputting 3.

It seems like TestRecord is unable to infer its generic type when the value is wrapped in a Record. Is this expected? Am I doing something wrong?

void main() {
  final testNoRecord = TestNoRecord('foo', (val) => print(val.length));
  testNoRecord.go();

  final test = TestRecord(('foo',), (val) => print(val.length));
  test.go();

  final test2 = TestRecordWorkaround('foo', (val) => print(val.length));
  test2.go();
}

class TestNoRecord<T> {
  final T val;
  final Function(T) fn;
  TestNoRecord(this.val, this.fn);

  void go() {
    fn(val);
  }
}

class TestRecord<T> {
  final (T,) record;
  final Function(T) fn;

  TestRecord(this.record, this.fn);

  void go() {
    final (val,) = record;
    fn(val);
  }
}

class TestRecordWorkaround<T> {
  final (T,) record;
  final Function(T) fn;

  TestRecordWorkaround(T val, this.fn) : record = (val,);

  void go() {
    final (val,) = record;
    fn(val);
  }
}

What error do you get if it fails at compile time?

Yes, it won’t recognize the type explicitly.

This is how you’d do it.

final test = TestRecord(('foo',), (String val) => print(val.length));

Interestingly, there’s a similar warning in Flutter on the showDialog<T> method where you need to specify the Widget type back.

I think this is an analyzer bug. CFE accepts the program without an issue.

I filed analyzer issue with inference involving record types · Issue #59918 · dart-lang/sdk · GitHub about the discrepancy.

4 Likes

Thanks everyone for taking a look!

@mraleph you’re right, it does seem to be an issue with the analyzer. Thank you for distilling the repro case in your issue!

@Callmephil yeah, I understand that you can specify the type in the function definition, but you don’t have to do that if the generic type is inferred correctly, because the fn parameter is specified to be a Function(T).

1 Like

The problem with TestRecord lies in Dart’s type inference: The generic type T is not correctly derived from the record (T), which causes the type of val in the callback function to become Object? This happens because Dart does not have enough context to determine T uniquely. In TestRecordWorkaround, however, T is explicitly derived via the constructor parameter (val), which solves the problem. The behavior is to be expected, since Dart sometimes needs more context for generic types with records.

Wrong answer. It’s a bug and it is actually fixed now.

7 Likes