Skip to content

Commit 4b3b390

Browse files
authored
Add LUB computation for class types (#2594)
BREAKING CHANGE: Binary and ternary expressions now compute and evaluate to the least upper bound of two not identical class type inputs in the absence of a better fitting contextual type. Technically a breaking change, yet likely without noticeable effects on existing code.
1 parent a1434b2 commit 4b3b390

File tree

9 files changed

+782
-288
lines changed

9 files changed

+782
-288
lines changed

Diff for: src/compiler.ts

+56-27
Original file line numberDiff line numberDiff line change
@@ -3779,7 +3779,7 @@ export class Compiler extends DiagnosticEmitter {
37793779

37803780
rightExpr = this.compileExpression(right, leftType);
37813781
rightType = this.currentType;
3782-
commonType = Type.commonDenominator(leftType, rightType, true);
3782+
commonType = Type.commonType(leftType, rightType, contextualType, true);
37833783
if (!commonType || !commonType.isNumericValue) {
37843784
this.error(
37853785
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
@@ -3814,7 +3814,7 @@ export class Compiler extends DiagnosticEmitter {
38143814

38153815
rightExpr = this.compileExpression(right, leftType);
38163816
rightType = this.currentType;
3817-
commonType = Type.commonDenominator(leftType, rightType, true);
3817+
commonType = Type.commonType(leftType, rightType, contextualType, true);
38183818
if (!commonType || !commonType.isNumericValue) {
38193819
this.error(
38203820
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
@@ -3849,7 +3849,7 @@ export class Compiler extends DiagnosticEmitter {
38493849

38503850
rightExpr = this.compileExpression(right, leftType);
38513851
rightType = this.currentType;
3852-
commonType = Type.commonDenominator(leftType, rightType, true);
3852+
commonType = Type.commonType(leftType, rightType, contextualType, true);
38533853
if (!commonType || !commonType.isNumericValue) {
38543854
this.error(
38553855
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
@@ -3884,7 +3884,7 @@ export class Compiler extends DiagnosticEmitter {
38843884

38853885
rightExpr = this.compileExpression(right, leftType);
38863886
rightType = this.currentType;
3887-
commonType = Type.commonDenominator(leftType, rightType, true);
3887+
commonType = Type.commonType(leftType, rightType, contextualType, true);
38883888
if (!commonType || !commonType.isNumericValue) {
38893889
this.error(
38903890
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
@@ -3921,7 +3921,7 @@ export class Compiler extends DiagnosticEmitter {
39213921

39223922
rightExpr = this.compileExpression(right, leftType);
39233923
rightType = this.currentType;
3924-
commonType = Type.commonDenominator(leftType, rightType, false);
3924+
commonType = Type.commonType(leftType, rightType, contextualType);
39253925
if (!commonType) {
39263926
this.error(
39273927
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
@@ -3973,7 +3973,7 @@ export class Compiler extends DiagnosticEmitter {
39733973

39743974
rightExpr = this.compileExpression(right, leftType);
39753975
rightType = this.currentType;
3976-
commonType = Type.commonDenominator(leftType, rightType, false);
3976+
commonType = Type.commonType(leftType, rightType, contextualType);
39773977
if (!commonType) {
39783978
this.error(
39793979
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
@@ -4038,7 +4038,7 @@ export class Compiler extends DiagnosticEmitter {
40384038
} else {
40394039
rightExpr = this.compileExpression(right, leftType);
40404040
rightType = this.currentType;
4041-
commonType = Type.commonDenominator(leftType, rightType, false);
4041+
commonType = Type.commonType(leftType, rightType, contextualType);
40424042
if (!commonType || !commonType.isNumericValue) {
40434043
this.error(
40444044
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
@@ -4083,7 +4083,7 @@ export class Compiler extends DiagnosticEmitter {
40834083
} else {
40844084
rightExpr = this.compileExpression(right, leftType);
40854085
rightType = this.currentType;
4086-
commonType = Type.commonDenominator(leftType, rightType, false);
4086+
commonType = Type.commonType(leftType, rightType, contextualType);
40874087
if (!commonType || !leftType.isNumericValue) {
40884088
this.error(
40894089
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
@@ -4128,7 +4128,7 @@ export class Compiler extends DiagnosticEmitter {
41284128
} else {
41294129
rightExpr = this.compileExpression(right, leftType);
41304130
rightType = this.currentType;
4131-
commonType = Type.commonDenominator(leftType, rightType, false);
4131+
commonType = Type.commonType(leftType, rightType, contextualType);
41324132
if (!commonType || !commonType.isNumericValue) {
41334133
this.error(
41344134
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
@@ -4173,7 +4173,7 @@ export class Compiler extends DiagnosticEmitter {
41734173
} else {
41744174
rightExpr = this.compileExpression(right, leftType);
41754175
rightType = this.currentType;
4176-
commonType = Type.commonDenominator(leftType, rightType, false);
4176+
commonType = Type.commonType(leftType, rightType, contextualType);
41774177
if (!commonType || !commonType.isNumericValue) {
41784178
this.error(
41794179
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
@@ -4218,7 +4218,7 @@ export class Compiler extends DiagnosticEmitter {
42184218
} else {
42194219
rightExpr = this.compileExpression(right, leftType);
42204220
rightType = this.currentType;
4221-
commonType = Type.commonDenominator(leftType, rightType, false);
4221+
commonType = Type.commonType(leftType, rightType, contextualType);
42224222
if (!commonType || !commonType.isNumericValue) {
42234223
this.error(
42244224
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
@@ -4263,7 +4263,7 @@ export class Compiler extends DiagnosticEmitter {
42634263
} else {
42644264
rightExpr = this.compileExpression(right, leftType);
42654265
rightType = this.currentType;
4266-
commonType = Type.commonDenominator(leftType, rightType, false);
4266+
commonType = Type.commonType(leftType, rightType, contextualType);
42674267
if (!commonType || !commonType.isNumericValue) {
42684268
this.error(
42694269
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
@@ -4390,7 +4390,7 @@ export class Compiler extends DiagnosticEmitter {
43904390
} else {
43914391
rightExpr = this.compileExpression(right, leftType);
43924392
rightType = this.currentType;
4393-
commonType = Type.commonDenominator(leftType, rightType, false);
4393+
commonType = Type.commonType(leftType, rightType, contextualType);
43944394
if (!commonType || !commonType.isIntegerValue) {
43954395
this.error(
43964396
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
@@ -4435,7 +4435,7 @@ export class Compiler extends DiagnosticEmitter {
44354435
} else {
44364436
rightExpr = this.compileExpression(right, leftType);
44374437
rightType = this.currentType;
4438-
commonType = Type.commonDenominator(leftType, rightType, false);
4438+
commonType = Type.commonType(leftType, rightType, contextualType);
44394439
if (!commonType || !commonType.isIntegerValue) {
44404440
this.error(
44414441
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
@@ -4480,7 +4480,7 @@ export class Compiler extends DiagnosticEmitter {
44804480
} else {
44814481
rightExpr = this.compileExpression(right, leftType);
44824482
rightType = this.currentType;
4483-
commonType = Type.commonDenominator(leftType, rightType, false);
4483+
commonType = Type.commonType(leftType, rightType, contextualType);
44844484
if (!commonType || !commonType.isIntegerValue) {
44854485
this.error(
44864486
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
@@ -4537,8 +4537,21 @@ export class Compiler extends DiagnosticEmitter {
45374537
this.currentType = Type.bool;
45384538

45394539
} else {
4540-
rightExpr = this.compileExpression(right, leftType, inheritedConstraints | Constraints.ConvImplicit);
4540+
rightExpr = this.compileExpression(right, leftType, inheritedConstraints);
45414541
rightType = this.currentType;
4542+
commonType = Type.commonType(leftType, rightType, contextualType);
4543+
if (!commonType) {
4544+
this.error(
4545+
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
4546+
expression.range, "&&", leftType.toString(), rightType.toString()
4547+
);
4548+
this.currentType = contextualType;
4549+
return module.unreachable();
4550+
}
4551+
leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left);
4552+
leftType = commonType;
4553+
rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right);
4554+
rightType = commonType;
45424555

45434556
// simplify if copying left is trivial
45444557
if (expr = module.tryCopyTrivialExpression(leftExpr)) {
@@ -4562,7 +4575,7 @@ export class Compiler extends DiagnosticEmitter {
45624575
flow.mergeBranch(rightFlow); // LHS && RHS -> RHS conditionally executes
45634576
flow.noteThen(expr, rightFlow); // LHS && RHS == true -> RHS always executes
45644577
this.currentFlow = flow;
4565-
this.currentType = leftType;
4578+
this.currentType = commonType;
45664579
}
45674580
break;
45684581
}
@@ -4603,8 +4616,22 @@ export class Compiler extends DiagnosticEmitter {
46034616
this.currentType = Type.bool;
46044617

46054618
} else {
4606-
rightExpr = this.compileExpression(right, leftType, inheritedConstraints | Constraints.ConvImplicit);
4619+
rightExpr = this.compileExpression(right, leftType, inheritedConstraints);
46074620
rightType = this.currentType;
4621+
commonType = Type.commonType(leftType, rightType, contextualType);
4622+
if (!commonType) {
4623+
this.error(
4624+
DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2,
4625+
expression.range, "||", leftType.toString(), rightType.toString()
4626+
);
4627+
this.currentType = contextualType;
4628+
return module.unreachable();
4629+
}
4630+
let possiblyNull = leftType.is(TypeFlags.Nullable) && rightType.is(TypeFlags.Nullable);
4631+
leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left);
4632+
leftType = commonType;
4633+
rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right);
4634+
rightType = commonType;
46084635

46094636
// simplify if copying left is trivial
46104637
if (expr = module.tryCopyTrivialExpression(leftExpr)) {
@@ -4629,7 +4656,9 @@ export class Compiler extends DiagnosticEmitter {
46294656
flow.mergeBranch(rightFlow); // LHS || RHS -> RHS conditionally executes
46304657
flow.noteElse(expr, rightFlow); // LHS || RHS == false -> RHS always executes
46314658
this.currentFlow = flow;
4632-
this.currentType = leftType;
4659+
this.currentType = possiblyNull
4660+
? commonType
4661+
: commonType.nonNullableType;
46334662
}
46344663
break;
46354664
}
@@ -8945,7 +8974,7 @@ export class Compiler extends DiagnosticEmitter {
89458974

89468975
private compileTernaryExpression(
89478976
expression: TernaryExpression,
8948-
ctxType: Type,
8977+
contextualType: Type,
89498978
constraints: Constraints
89508979
): ExpressionRef {
89518980
let module = this.module;
@@ -8958,24 +8987,24 @@ export class Compiler extends DiagnosticEmitter {
89588987
// FIXME: skips common denominator, inconsistently picking branch type
89598988
let condKind = this.evaluateCondition(condExprTrueish);
89608989
if (condKind == ConditionKind.True) {
8961-
return module.maybeDropCondition(condExprTrueish, this.compileExpression(ifThen, ctxType));
8990+
return module.maybeDropCondition(condExprTrueish, this.compileExpression(ifThen, contextualType));
89628991
}
89638992
if (condKind == ConditionKind.False) {
8964-
return module.maybeDropCondition(condExprTrueish, this.compileExpression(ifElse, ctxType));
8993+
return module.maybeDropCondition(condExprTrueish, this.compileExpression(ifElse, contextualType));
89658994
}
89668995

89678996
let outerFlow = this.currentFlow;
89688997
let ifThenFlow = outerFlow.forkThen(condExpr);
89698998
this.currentFlow = ifThenFlow;
8970-
let ifThenExpr = this.compileExpression(ifThen, ctxType);
8999+
let ifThenExpr = this.compileExpression(ifThen, contextualType);
89719000
let ifThenType = this.currentType;
89729001

89739002
let ifElseFlow = outerFlow.forkElse(condExpr);
89749003
this.currentFlow = ifElseFlow;
8975-
let ifElseExpr = this.compileExpression(ifElse, ctxType == Type.auto ? ifThenType : ctxType);
9004+
let ifElseExpr = this.compileExpression(ifElse, contextualType == Type.auto ? ifThenType : contextualType);
89769005
let ifElseType = this.currentType;
89779006

8978-
if (ctxType == Type.void) { // values, including type mismatch, are irrelevant
9007+
if (contextualType == Type.void) { // values, including type mismatch, are irrelevant
89799008
if (ifThenType != Type.void) {
89809009
ifThenExpr = module.drop(ifThenExpr);
89819010
ifThenType = Type.void;
@@ -8986,13 +9015,13 @@ export class Compiler extends DiagnosticEmitter {
89869015
}
89879016
this.currentType = Type.void;
89889017
} else {
8989-
let commonType = Type.commonDenominator(ifThenType, ifElseType, false);
9018+
let commonType = Type.commonType(ifThenType, ifElseType, contextualType);
89909019
if (!commonType) {
89919020
this.error(
89929021
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
89939022
ifElse.range, ifElseType.toString(), ifThenType.toString()
89949023
);
8995-
this.currentType = ctxType;
9024+
this.currentType = contextualType;
89969025
return module.unreachable();
89979026
}
89989027
ifThenExpr = this.convertExpression(ifThenExpr, ifThenType, commonType, false, ifThen);

Diff for: src/module.ts

+40
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,46 @@ export namespace HeapTypeRef {
128128
export function isSubtype(ht: HeapTypeRef, superHt: HeapTypeRef): bool {
129129
return binaryen._BinaryenHeapTypeIsSubType(ht, superHt);
130130
}
131+
132+
export function leastUpperBound(a: HeapTypeRef, b: HeapTypeRef): HeapTypeRef {
133+
// see binaryen/src/wasm/wasm-type.cpp
134+
if (a == b) return a;
135+
if (getBottom(a) != getBottom(b)) return -1;
136+
if (isBottom(a)) return b;
137+
if (isBottom(b)) return a;
138+
if (a > b) {
139+
let t = a;
140+
a = b;
141+
b = t;
142+
}
143+
switch (a) {
144+
case HeapTypeRef.Extern:
145+
case HeapTypeRef.Func: return -1;
146+
case HeapTypeRef.Any: return a;
147+
case HeapTypeRef.Eq: {
148+
return b == HeapTypeRef.I31 || b == HeapTypeRef.Data || b == HeapTypeRef.Array
149+
? HeapTypeRef.Eq
150+
: HeapTypeRef.Any;
151+
}
152+
case HeapTypeRef.I31: {
153+
return b == HeapTypeRef.Data || b == HeapTypeRef.Array
154+
? HeapTypeRef.Eq
155+
: HeapTypeRef.Any;
156+
}
157+
case HeapTypeRef.Data: {
158+
return b == HeapTypeRef.Array
159+
? HeapTypeRef.Data
160+
: HeapTypeRef.Any;
161+
}
162+
case HeapTypeRef.Array:
163+
case HeapTypeRef.String:
164+
case HeapTypeRef.StringviewWTF8:
165+
case HeapTypeRef.StringviewWTF16:
166+
case HeapTypeRef.StringviewIter: return HeapTypeRef.Any;
167+
}
168+
assert(false);
169+
return -1;
170+
}
131171
}
132172

133173
/** Packed array element respectively struct field types. */

Diff for: src/program.ts

+21
Original file line numberDiff line numberDiff line change
@@ -4415,6 +4415,27 @@ export class Class extends TypedElement {
44154415
registerConcreteElement(program, this);
44164416
}
44174417

4418+
/** Computes the least upper bound of two class types. */
4419+
static leastUpperBound(a: Class, b: Class): Class | null {
4420+
if (a == b) return a;
4421+
let candidates = new Set<Class>();
4422+
candidates.add(a);
4423+
candidates.add(b);
4424+
while (true) {
4425+
let aBase = a.base;
4426+
let bBase = b.base;
4427+
if (!aBase && !bBase) return null; // none
4428+
if (aBase) {
4429+
if (candidates.has(aBase)) return aBase;
4430+
candidates.add(a = aBase);
4431+
}
4432+
if (bBase) {
4433+
if (candidates.has(bBase)) return bBase;
4434+
candidates.add(b = bBase);
4435+
}
4436+
}
4437+
}
4438+
44184439
/** Sets the base class. */
44194440
setBase(base: Class): void {
44204441
assert(!this.base);

0 commit comments

Comments
 (0)