dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.09k stars 1.56k forks source link

NoSuchMethodError when calling extension method against a dynamic #41094

Closed awhitford closed 4 years ago

awhitford commented 4 years ago

Consider the following code that extends GeoPoint to add a translation method toGeoFirePoint:

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:geoflutterfire/geoflutterfire.dart';

extension GeoPoints on GeoPoint {
  /// Translate a [GeoPoint] into a [GeoFirePoint]
  GeoFirePoint toGeoFirePoint() => GeoFirePoint(latitude, longitude);
}

This extension does not seem to be recognized when the value is dynamic (even though it matches the correct type at runtime). I am receiving a NoSuchMethodError:

flutter: NoSuchMethodError: Class 'GeoPoint' has no instance method 'toGeoFirePoint'.
Receiver: Instance of 'GeoPoint'
Tried calling: toGeoFirePoint()

Note that the code does successfully compile. Besides the NoSuchMethodError runtime error, another symptom is that VS Code is not able to recognize the method either.

This code fails:

  factory MyObject.fromSnapshot(DocumentSnapshot snapshot) {
    return MyObject.fromMap({
      ...snapshot.data,
      'geoLocation': snapshot['geoLocation']['geopoint']?.toGeoFirePoint(),
    }, snapshot.reference);
  }

This code works:

  factory MyObject.fromSnapshot(DocumentSnapshot snapshot) {
    final GeoPoint gp = snapshot['geoLocation']['geopoint'];
    return MyObject.fromMap({
      ...snapshot.data,
      'geoLocation': gp?.toGeoFirePoint(),
    }, snapshot.reference);
  }

The difference between the two code snippets is that snapshot['geoLocation']['geopoint'] has a dynamic type at compile time, so the first code snippet is trying to call toGeoFirePoint() on a dynamic and it isn't finding the extension method, while the second code snippet knows how to resolve the extension method.

I am using Dart 2.7.0 on a MacOS 10.15.3.

awhitford commented 4 years ago

Note that adding a cast did not solve the issue either:

snapshot['geoLocation']['geopoint'].cast<GeoPoint>()?.toGeoFirePoint()

I would have thought that the cast would constrain the type so that the compiler could successfully apply the extension method, but that did not help.

mraleph commented 4 years ago

Extension methods are based on the static type of the receiver - so this is working as intended. They are not dispatched dynamically.

If you want to down cast an expression of super type to a subtype you should use as and not cast, e.g.

(snapshot['geoLocation']['geopoint'] as GeoPoint)?.toGeoFirePoint()

cast<T> is a method on Iterable which is essentially equivalent to .map((e) => e as T).

awhitford commented 4 years ago

@mraleph Thank you for the quick clarification. I see now that the documentation clearly states, "You can’t invoke extension methods on variables of type dynamic."