enso-org / enso

Enso Analytics is a self-service data prep and analysis platform designed for data teams.
https://ensoanalytics.com
Apache License 2.0
7.37k stars 323 forks source link

Unable to Resolve Methods on User-Defined Types #1662

Closed sylwiabr closed 2 years ago

sylwiabr commented 3 years ago

@sylwiabr commented on Fri Apr 09 2021

What did you do?

opened project:

from Standard.Base import all
import Standard.Base.Math

type Shape
    type Circle r
    type Square a

    surface = case this of
        Circle r -> Math.pi * r ^ 2
        Square a -> a ^ 2

main =
    c = Circle 12
    f = c.surface

then added a node Square 2 and another one connected with itsurface

What did you expect to see?

both surfaces calculated

What did you see instead?

Screenshot 2021-04-09 at 20 53 24

Enso Version

Frontend: Version: 1.0.0 Build: 9b1e455 Electron: 11.1.1 Chrome: 87.0.4280.88

Backend: Enso Project Manager Version: 0.2.10 Built with: scala-2.13.5 for GraalVM 21.0.0.2 Built from: enso-0.2.10 @ cd1ad23d392cb7d6380c27f676dd8448f7df8827 Built on: Mac OS X (amd64)

Additional notes

Screenshot 2021-04-09 at 21 07 48

I can change value on node with Circle but if I put another surface method connected to it it is returning the same error

4e6 commented 3 years ago

The method resolution fails when the value of an atom, i.e. Circle 12 is obtained from the cache and not evaluated. Apparently, evaluation changes the runtime state affecting the method resolution on other nodes.

4e6 commented 3 years ago

Update. AtomConstructor when evaluated, registers its methods in the scope. The fix would be to generate methods defined on atoms during the IrToTruffle compilation step.

4e6 commented 3 years ago

Update 2 Cached Atom carries its own scope. Scope stores methods in a hashmap with an AtomConstructor key. The lookup fails to find an AtomConstructor because the map contains an atom constructor from the previous compilation (the lookup is reference-based)

iamrecursion commented 3 years ago

This is my analysis of the problem from when I was looking into it in May.

Investigation

Interestingly enough, the module handle appears to be the same at every point in the above chain, so far. The issue, however, occurs in the next phase as the compiler performs stub generation (which involves regenerating atom constructors anew).

Potential Solutions

The following are potential solutions to the issue that may not necessarily work.

AtomConstructor Replacement in the Cache

An initial thought of mine was that we could replace the scopes for relevant atoms in the cache, but this has two main issues:

  1. All cache entries are stored as opaque Objects. Whilst we can do a pattern match and replacement if the entry is an Atom, we then run into the second issue.
  2. Types that have their scopes invalidated may occur inside other scopes, meaning that the replacement process would have to traverse potentially the entire cached object graph. This would be far too expensive to perform.

AtomConstructor Reuse

The ideal solution would be to reuse the existing AtomConstructor instances rather than generating them anew. From a first glance this has a few problems:

Perhaps, if there was a way to retain information on which AtomConstructors were alive this would be better, but that would involve deep analysis of the cache, which is likely too expensive to perform in general.

Wholesale Cache Eviction

Unfortunately the only truly semantically correct solution may be to clear the cache when any module imported into the current module is changed in a way that changes the types it defines. This is a very heavyweight solution, however, and I would much prefer to find something more reliable. Fundamentally, however, we run into the following issues with any partial solution:

This means that the only semantically correct full solution is one that does the following:

I'm not sure we can even do better in the future.