Skip to content

After all these years, should we support d() -> d.call() even in the case where call is a getter? #3482

@eernstg

Description

@eernstg

[Edit: The language team decided on Jan 3 2024 that the answer is "No" (see this comment).]

See dart-lang/sdk#51517 for some background information.

The language specification has specified for at least 6 years that an invocation of the form e<typeArgs>(args) where e has static type dynamic must handle a callee o (the value of e) which is not a function object in the following way:

  • Check that o is an instance of an interface type that has a method named call.
  • Execute o.call<typeArgs>(args).

(and this includes a similar rule where there are no actual type arguments and/or no actual value arguments because "typeArgs and args can be empty".)

However, the implementations (at least the VM, the JavaScript output from dart compile js, and the executable provided by dart compile exe, on x64) supports the following scenario as well:

  • Check that o is an instance of an interface type that has a getter named call.
  • Invoke that getter, o.call, to obtain an object fo.
  • Execute fo<typeArgs>(args) (which may run the same algorithm recursively).

The language team has had discussions about this behavior previously (a long time ago), and did not support the generalization. That is, the language team does not want the second scenario to be supported.

However, many backends (perhaps even all of them?) support the second scenario, and @mraleph suggested in dart-lang/sdk#51517 that we should change the specification such that the second scenario is allowed: (1) This is the most convenient decision because this behavior is implemented today, and (2) it would be a breaking change to stop supporting scenario 2.

I believe we had some arguments about the potential performance implications of supporting scenario 2, but at this time I can't see any reason why this would be important. In particular, I can't see why supporting scenario 2 would cause function invocation to be slower in all cases, or function objects would occupy more space in all cases; it seems more likely that it just makes dynamic function invocations more expensive, and they are already allowed to be expensive because we assume that they are rare (and should be even rarer ;-).

@dart-lang/language-team, WDYT? Should we change the specification such that scenario 2 is supported? Alternatively, should we initiate a breaking change process about scenario 2 being unsupported in the future?

@mkustermann, @mraleph, @sigmundch, @nshahan, @askeksa, @osa1, WDYT? Are you aware of any optimization opportunities that are made impossible in the case where the language supports scenario 2? And assuming that it is correct that we do this already, it's more like: Are you aware of any optimization opportunities that we could benefit from in the future if we stop supporting scenario 2?

@johnniwinther, WDYT? As far as I understand @osa1's comment, the CFE generates code where it is impossible to make the distinction between the invocation of a method and the invocation of a getter that yields a function object which is subsequently invoked. I thought that this had now been separated out into two distinct kinds of Kernel code, and also that it had been beneficial for function invocation performance in general to make this distinction explicit in Kernel code. Is this situation special because it is an invocation of an object of type dynamic?

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions