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

Dynamic variable values can utilize extension functionality. #4225

Open
Stitch-Taotao opened this issue Jan 8, 2025 · 13 comments
Open

Dynamic variable values can utilize extension functionality. #4225

Stitch-Taotao opened this issue Jan 8, 2025 · 13 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@Stitch-Taotao
Copy link

Dynamic variable values can utilize extension functionality.

It is mainly used to act like Kotlin's let extension function, making it convenient to check whether a dynamic value is null or to check its type.

extension MExt<T> on T {
  void let(void Function(T it) block) {
    if (this != null) {
      block(this);
    }
  }

  void letType<R>(void Function(R it) block) {
    if (this is R) {
      block(this as R);
    }
  }
}
void main(){
  dynamic j = 100;
  j.let((it) {
    print("print j");
  });
  j.letType<int>((it){
    print("j is int");
  });
}

The biggest problem now is that it works perfectly fine when running variables of other types, but a NoSuchMethodError will be reported when running the values of dynamic variables.

You can see more in :Dartpad demo

@Stitch-Taotao Stitch-Taotao added the feature Proposed language feature that solves one or more problems label Jan 8, 2025
@ykmnkmi
Copy link

ykmnkmi commented Jan 8, 2025

https://dart.dev/language/extension-methods#static-types-and-dynamic

You can't invoke extension methods on variables of type dynamic. For example, the following code results in a runtime exception:

dynamic d = '2';
print(d.parseInt()); // Runtime exception: NoSuchMethodError

The reason that dynamic doesn't work is that extension methods are resolved against the static type of the receiver. Because extension methods are resolved statically, they're as fast as calling a static function.

@lrhn
Copy link
Member

lrhn commented Jan 9, 2025

Should we allow extension method invocations on dynamic?

It would still be a top type, so only extensions that work on all objects would apply.
It would still have to first try to do a dynamic instance member invocation, and only if there is no member should it fall back on a single applicable extension on a top type.

It's probably a bad idea.
It would potentially increase the size of every dynamic invocation.
It wouldn't run the extension that would apply to the runtime type of the target, which is otherwise how dynamic invocations work.
And the author can always cast to Object? if they want to use extension methods.

Maybe the analyzer could warm if a dynamic never invocation has a name that is probably meant to be an extension member (there is one in scope that would apply, and no obvious instance members with the same name.)

@rrousselGit
Copy link

IMO dynamic should be deprecated anyway. Most use-cases of dynamic can be replaced with Object?.

And there's a lint for catching dynamic invocations. As such, the linter would tell you that dynamic.myExtension() is a dynamic invocation instead of an extension call.

@lrhn
Copy link
Member

lrhn commented Jan 9, 2025

Using the avoid_dynamic_calls lint will help you here too.
(And if you have that lint, there is no reason to deprecate or remove dynamic, it can keep existing for the few use-cases that can't be solved without it.)

@jakemac53
Copy link
Contributor

(And if you have that lint, there is no reason to deprecate or remove dynamic, it can keep existing for the few use-cases that can't be solved without it.)

I am genuinely curious what these are and I do think we should be asking ourselves how important they are. Having dynamic invocations dramatically complicates the language, with every feature having to take them into account, and I can't recall ever actually needing one, they just let you be lazy.

@rrousselGit
Copy link

+1
I'd rather add a dedicated language feature where those are used.

There's one dynamic usage in the SDK that I'm aware of: jsonEncode.
It does a dynamic.toJson() call.

@jakemac53
Copy link
Contributor

It does a dynamic.toJson() call.

And this is a horrible API lol. It should have exposed an interface for that and then done a type check.

@mateusfccp
Copy link
Contributor

It does a dynamic.toJson() call.

And this is a horrible API lol. It should have exposed an interface for that and then done a type check.

Unfortunately, it's too breaking to change it now, so I don't see it happening...

@jakemac53
Copy link
Contributor

Unfortunately, it's too breaking to change it now, so I don't see it happening...

Sure, I agree with that in general. Removing dynamic as a whole falls into this category almost certainly, at least until a breaking version of the language.

@Wdestroier
Copy link

My hope is to be able to add methods in classes with augmentations or macros, then extension types working with dynamic would be possible. I'm not sure if augmentations will be powerful enough. For example: we are not allowed to add a letType method in a top type, if I remember correctly.

@rrousselGit
Copy link

There are a few other dynamic usages in the SDK 🔥
In particular in the test package. For example expect(..., isEmpty) works by doing a dynamic.isEmpty call.

I'm quite proud of knowing such useless information :p


Still, I think deprecating dynamic invocation would be very doable.
We wouldn't straight up remove it as that's a breaking change. But we can certainly tell all Dart users that they shouldn't use rely on that feature.

This could be done by making sure the lint discouraging dynamic invocations is enabled by default (and possibly with a tweaked message)

@ykmnkmi
Copy link

ykmnkmi commented Jan 10, 2025

There are also dynamic class fields in the SDK, like var fieldName;.

@mateusfccp
Copy link
Contributor

There are a few other dynamic usages in the SDK 🔥 In particular in the test package. For example expect(..., isEmpty) works by doing a dynamic.isEmpty call.

I'm quite proud of knowing such useless information :p

Still, I think deprecating dynamic invocation would be very doable. We wouldn't straight up remove it as that's a breaking change. But we can certainly tell all Dart users that they shouldn't use rely on that feature.

This could be done by making sure the lint discouraging dynamic invocations is enabled by default (and possibly with a tweaked message)

I think we could deprecate dynamic completely as long as we have some kind of structural-typing. So, for instance, we could introduce a JsonEncodable interface with a toJson method and check for it within jsonEncode. Objects wouldn't need to implement the interface directly, as it is structural. It would be way less breaking, as it would only be breaking if the passed argument is itself dynamic. I don't think it's worthy the effort, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

7 participants