sdk
sdk copied to clipboard
Analyzer incorrectly infers type StreamController<dynamic> on explicitly typed field
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.keyparameter toElementStream - Remove
ElementStream.add - Remove
ElementStream.stream - Rename the folder
lib/src/streamtolib/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();
}
}