sdk icon indicating copy to clipboard operation
sdk copied to clipboard

Analyzer incorrectly infers type StreamController<dynamic> on explicitly typed field

Open swrenn opened this issue 3 years ago • 0 comments

dart analyze incorrectly infers the type StreamController<dynamic> on a field explicitly typed as StreamController<ElementData>. See ElementStream.controller in the example below. This causes the analyzer to flag the assignment in MyWidget as invalid. The code compiles.

If I change almost anything about the example below, the analyzer will infer the correct type for ElementStream.controller. For example:

  • Add a super.key parameter to ElementStream
  • Remove ElementStream.add
  • Remove ElementStream.stream
  • Rename the folder lib/src/stream to lib/src/streams
  • Remove either field from ElementData
  • Remove any of the elements of the enum ElementState

To reproduce, place the following files in a new Flutter project. This is an odd one. Have fun!

dart analyze

Analyzing example...                   1.1s

  error • lib/src/stream/my_widget.dart:13:49 • A value of type 'StreamController<dynamic>?' can't be assigned to a variable of type 'StreamController<ElementData>?'. Try changing the type of the variable, or casting the
          right-hand type to 'StreamController<ElementData>?'. • invalid_assignment
   info • lib/src/stream/element_stream.dart:8:3 • Use key in widget constructors. • use_key_in_widget_constructors
   info • lib/src/stream/my_widget.dart:13:36 • The value of the local variable 'controller' isn't used. Try removing the variable or using it. • unused_local_variable

3 issues found.

lib/main.dart

import 'dart:async';

import 'package:flutter/material.dart';

import 'src/stream/element_data.dart';
import 'src/stream/element_stream.dart';
import 'src/stream/my_widget.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MainScreen(),
    );
  }
}

class MainScreen extends StatelessWidget {
  const MainScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: ElementStream(
        controller: StreamController<ElementData>.broadcast(),
        child: const MyWidget(),
      ),
    );
  }
}

lib/src/stream/element_data.dart

enum ElementState {
  none,
  unmount,
  mount,
}

class ElementData {
  const ElementData({
    required this.index,
    required this.state,
  });

  final int index;
  final ElementState state;
}

lib/src/stream/element_stream.dart

import 'dart:async';

import 'package:flutter/widgets.dart';

import 'element_data.dart';

class ElementStream extends InheritedWidget {
  const ElementStream({
    required this.controller,
    required super.child,
  });

  static ElementStream? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ElementStream>();
  }

  final StreamController<ElementData> controller; // ERROR: Inferred as StreamController<dynamic>
  Stream<ElementData> get stream => controller.stream;

  void add(ElementData data) {
    controller.add(data);
  }

  @override
  bool updateShouldNotify(covariant ElementStream oldWidget) {
    return oldWidget.controller != controller;
  }
}

lib/src/stream/my_widget.dart

import 'dart:async';

import 'package:flutter/material.dart';

import 'element_data.dart';
import 'element_stream.dart';

class MyWidget extends StatelessWidget {
  const MyWidget({super.key});

  @override
  Widget build(BuildContext context) {
    StreamController<ElementData>? controller = ElementStream.of(context)?.controller; // ERROR: Flagged as invalid
    return const SizedBox();
  }
}

swrenn avatar Aug 08 '22 15:08 swrenn