assemblyscript icon indicating copy to clipboard operation
assemblyscript copied to clipboard

feat: add type narrow to support better type check

Open HerrCai0907 opened this issue 2 years ago • 11 comments

HerrCai0907 avatar Jun 30 '22 15:06 HerrCai0907

When I thought about narrowing in the past, the mechanism I had in mind was to add a new Flow#localTypes in addition to Flow#localFlags, that would include narrowed types if available. Checks would then first look if there is a narrowed type in Flow#localTypes and otherwise fall back to the annotated type from before (say through a Flow#getLocalType(index) helper). Wondering if going this route would have advantages over a separate concept as proposed here?

dcodeIO avatar Jun 30 '22 16:06 dcodeIO

When I thought about narrowing in the past, the mechanism I had in mind was to add a new Flow#localTypes in addition to Flow#localFlags, that would include narrowed types if available. Checks would then first look if there is a narrowed type in Flow#localTypes and otherwise fall back to the annotated type from before (say through a Flow#getLocalType(index) helper). Wondering if going this route would have advantages over a separate concept as proposed here?

I have not found localTypes in current flow. However it thinks like a good idea that we can reuse current inherit process. I can try to add this feature.

HerrCai0907 avatar Jul 01 '22 01:07 HerrCai0907

By the way, we should not support narrow type for variant like a.b. Although TS support this grammar, but it will cause something strange during running.

class A {}
class B extends A {
  foo(): void {}
}
class C {
  v: A = new A();
}

let c = new C();
let d = c;
c.v = new B();
if (c.v instanceof B) {
  d.v = new A(); // c.v has changed, but compiler don't know
  c.v.foo();
}

HerrCai0907 avatar Jul 01 '22 01:07 HerrCai0907

d.v = new A(); // c.v has changed, but compiler don't know

well. that's problem on TS. I guess they are fixed this later. I see no reason to refuse it if we can handle this case correctly

MaxGraey avatar Jul 01 '22 04:07 MaxGraey

Quite similar to why nullability checks are limited to locals. Also applies to narrowing if we don't want to run into unsoundness.

...
let cv = c.v;
if (cv instanceof B) {
  // works
}

dcodeIO avatar Jul 01 '22 11:07 dcodeIO

@dcodeIO @MaxGraey Could you kindly review this PR and new feature?

HerrCai0907 avatar Jul 05 '22 11:07 HerrCai0907

Still thinking whether the nullable flag should be combined with this, so the following can become one

    thenFlow.inheritNonnullIfTrue(condExpr);
    thenFlow.inheritNarrowedTypeIfTrue(condExpr);

Right now this looks like unnecessary duplication of basically the same concept (flag before, non-nullable narrowed type after).

dcodeIO avatar Jul 19 '22 17:07 dcodeIO

@dcodeIO Nonnull check has been merged into type narrow logic. Could you kindly review it?

HerrCai0907 avatar Aug 03 '22 16:08 HerrCai0907

Btw such flow mechanics is quite typical:

  var thenFlow = flow.fork();
  this.currentFlow = thenFlow;
  thenFlow.inheritNarrowedTypeIfTrue(condExpr);

I'm wondering could we add some method for Flow which include this routine?

class Flow {
  ...
  toBranch(condExpr: Expression, ifTrue: bool): Flow {
    var other = this.fork();
    if (ifTrue) {
      other.inheritNarrowedTypeIfTrue(condExpr);
    } else {
      other.inheritNarrowedTypeIfFalse(condExpr);
    }
    return other;
  }
}

And later simplify to:

this.currentFlow = flow.toBranch(condExpr, true);

Not sure about naming. Probably you could call all of this better

MaxGraey avatar Aug 05 '22 17:08 MaxGraey

@dcodeIO Could this feature merge?

HerrCai0907 avatar Aug 10 '22 13:08 HerrCai0907

@dcodeIO Could you kindly review this PR?

HerrCai0907 avatar Sep 05 '22 08:09 HerrCai0907

I'm having a hard time reviewing the PR, and would like to suggest to split it up a little. As a start, there has been the refactoring from fork + X to forkTrueBranch and forkFalseBranch. Can you make this a separate PR, so we have this in already as it seems useful independently? There also appear to be a few fixes here, which would be good to merge independently as well? Unless I missed another refactoring, afterwards we'd be left with just the narrowing changes.

From there on it gets a little tricky, as I am not confident about having an out-of-line src/narrow.ts. Typically, what we do there is to place utility data structures into util/, which here could be an util/narrowing.ts with the specialized data structure. However, the data structure now absorbs the logic from the inheritXY methods that previously lived in Flow, but I'd like to keep the logic in Flow. Suggesting to do the other ones first, so I can look at this again separately and see. Would this be OK for you?

dcodeIO avatar Sep 25 '22 17:09 dcodeIO

I'm having a hard time reviewing the PR, and would like to suggest to split it up a little.

Yes, I will split it into several PR. But it needs some time because I am preparing my graduation thesis now😄

HerrCai0907 avatar Sep 26 '22 01:09 HerrCai0907