feat(compiler): support unary operators for more accurate type checking (#37918)

Prior to this change, the unary + and - operators would be parsed as `x - 0`
and `0 - x` respectively. The runtime semantics of these expressions are
equivalent, however they may introduce inaccurate template type checking
errors as the literal type is lost, for example:

```ts
@Component({
  template: `<button [disabled]="isAdjacent(-1)"></button>`
})
export class Example {
  isAdjacent(direction: -1 | 1): boolean { return false; }
}
```

would incorrectly report a type-check error:

> error TS2345: Argument of type 'number' is not assignable to parameter
  of type '-1 | 1'.

Additionally, the translated expression for the unary + operator would be
considered as arithmetic expression with an incompatible left-hand side:

> error TS2362: The left-hand side of an arithmetic operation must be of
  type 'any', 'number', 'bigint' or an enum type.

To resolve this issues, the implicit transformation should be avoided.
This commit adds a new unary AST node to represent these expressions,
allowing for more accurate type-checking.

Fixes #20845
Fixes #36178

PR Close #37918
This commit is contained in:
JoostK
2020-07-04 01:52:40 +02:00
committed by Misko Hevery
parent e7da4040d6
commit 874792dc43
23 changed files with 313 additions and 23 deletions

View File

@ -138,6 +138,8 @@ const externalModuleIdentifier = new o.ExternalReference(anotherModuleUrl, 'some
const lhs = o.variable('lhs');
const rhs = o.variable('rhs');
expect(emitStmt(o.not(someVar).toStmt())).toEqual('!someVar;');
expect(emitStmt(o.unary(o.UnaryOperator.Minus, someVar).toStmt())).toEqual('(-someVar);');
expect(emitStmt(o.unary(o.UnaryOperator.Plus, someVar).toStmt())).toEqual('(+someVar);');
expect(emitStmt(o.assertNotNull(someVar).toStmt())).toEqual('someVar;');
expect(
emitStmt(someVar.conditional(o.variable('trueCase'), o.variable('falseCase')).toStmt()))

View File

@ -191,6 +191,8 @@ const externalModuleIdentifier = new o.ExternalReference(anotherModuleUrl, 'some
const rhs = o.variable('rhs');
expect(emitStmt(someVar.cast(o.INT_TYPE).toStmt())).toEqual('(<number>someVar);');
expect(emitStmt(o.not(someVar).toStmt())).toEqual('!someVar;');
expect(emitStmt(o.unary(o.UnaryOperator.Minus, someVar).toStmt())).toEqual('(-someVar);');
expect(emitStmt(o.unary(o.UnaryOperator.Plus, someVar).toStmt())).toEqual('(+someVar);');
expect(emitStmt(o.assertNotNull(someVar).toStmt())).toEqual('someVar!;');
expect(
emitStmt(someVar.conditional(o.variable('trueCase'), o.variable('falseCase')).toStmt()))