Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect the following situation during compile time - not during runtime #3385

Closed
mahop-net opened this issue Oct 7, 2023 · 3 comments
Closed
Labels
request Requests to resolve a particular developer problem

Comments

@mahop-net
Copy link

The code below throws the following runtime error on line 38:

Runtime Exception

_Exception has occurred.
TypeError (type '(AnyItem) => double' is not a subtype of type '(dynamic) => double')

It would be better, if the dart compiler detects this problem.

Problem can be solved by changeing line 37 to:
double getValue(AnyClass<T> c, T item) {
(Change parameter type AnyClass to AnyClass<T>)

Sample Code

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    var anyClass = AnyClass<AnyItem>(
      myFuntion: (item) => item.getDouble(),
    );

    var d = anyClass.anyClass2.getValue(anyClass, AnyItem(value: 5.0));
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Text("Hello $d"),
        ),
      ),
    );
  }
}

class AnyClass<T> {
  final double Function(T item) myFuntion;
  late AnyClass2<T> anyClass2;

  AnyClass({required this.myFuntion}) {
    anyClass2 = AnyClass2();
  }
}

class AnyClass2<T> {
  double getValue(AnyClass c, T item) {
    return c.myFuntion(item);
  }
}

class AnyItem {
  double value;

  AnyItem({required this.value});

  double getDouble() {
    return value;
  }
}

Screenshot of Exception

2023-10-07 14_24_57-main dart - flutter_generic_list_tester - Visual Studio Code

@mahop-net mahop-net added the feature Proposed language feature that solves one or more problems label Oct 7, 2023
@lrhn
Copy link
Member

lrhn commented Oct 7, 2023

It would be better, but it's not possible in the current language.
Dart class genetics are covariant, but AnyClass.function has a type where the class type parameter occurs contravariantly.

The only real solution to that would be to disallow such a field entirely.
That was considered too breaking when the type system for Dart 2 was introduced, so instead you're allowed to write the variable, and it's checked at runtime to avoid unsoundness.

The real solution would be variance annotations on type parameters, so that AnyClass could be contravariant in its type argument.

@lrhn lrhn added request Requests to resolve a particular developer problem and removed feature Proposed language feature that solves one or more problems labels Oct 7, 2023
@mahop-net
Copy link
Author

OK, thanks for your answer. It pointed me to the following issue where this problem is discussed since a couple of years...
Feature: Statically checked declaration-site variance

@eernstg
Copy link
Member

eernstg commented May 14, 2024

@mahop-net, I just saw this again and noticed that I'd like to add a couple of comments:

First, the basic issue is, as @lrhn mentioned, that AnyClass has a "contravariant member" (see #297 for more on this), namely myFuntion. The point is that the declared type of this member is double Function(T item), and the class type variable T occurs in this type as a parameter type, which is a contravariant position.

You shouldn't have any of those because they are veritable run-time failure factories. You can vote for dart-lang/sdk#59050 if you want to help indicating that those members should be flagged as problematic at compile time.

Next, here's how you can get the compile-time error that you mention:

Source code, usable today
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    var anyClass = AnyClass<AnyItem>(
      myFunction: (item) => item.getDouble(),
    );

    var d = anyClass.anyClass2.getValue(anyClass, AnyItem(value: 5.0));
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Text("Hello $d"),
        ),
      ),
    );
  }
}

typedef Inv<X> = X Function(X);

typedef AnyClass<T> = _AnyClass<T, Inv<T>>;

class _AnyClass<T, Invariance extends Inv<T>> {
  final double Function(T item) myFunction;
  late AnyClass2<T> anyClass2;

  _AnyClass({required this.myFunction}) {
    anyClass2 = AnyClass2();
  }
}

typedef AnyClass2<T> = _AnyClass2<T, Inv<T>>;

class _AnyClass2<T, Invariance extends Inv<T>> {
  double getValue(AnyClass<T> c, T item) {
    return c.myFunction(item);
  }
}

class AnyItem {
  double value;

  AnyItem({required this.value});

  double getDouble() {
    return value;
  }
}

I put the working source code first because it's more actionable. However, some gnarly details in the source code above are just there in order to emulate invariance. If we get the feature statically checked variance that you also mention then it can be expressed as follows:

Source code using statically checked variance
// Using `--enable-experiment=variance`

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    var anyClass = AnyClass<AnyItem>(
      myFunction: (item) => item.getDouble(),
    );

    var d = anyClass.anyClass2.getValue(anyClass, AnyItem(value: 5.0));
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Text("Hello $d"),
        ),
      ),
    );
  }
}

class AnyClass<inout T> {
  final double Function(T item) myFunction;
  late AnyClass2<T> anyClass2;

  AnyClass({required this.myFunction}) {
    anyClass2 = AnyClass2();
  }
}

class AnyClass2<T> {
  double getValue(AnyClass<T> c, T item) {
    return c.myFunction(item);
  }
}

class AnyItem {
  double value;

  AnyItem({required this.value});

  double getDouble() {
    return value;
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
request Requests to resolve a particular developer problem
Projects
None yet
Development

No branches or pull requests

3 participants