Skip to content

Commit 6da78bc

Browse files
authored
Fix special 'null' cases when inferring array literals (#1088)
1 parent e6d6cb9 commit 6da78bc

13 files changed

+385
-118
lines changed

src/compiler.ts

Lines changed: 25 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -7442,6 +7442,10 @@ export class Compiler extends DiagnosticEmitter {
74427442
return module.ref_null();
74437443
}
74447444
this.currentType = options.usizeType;
7445+
this.warning(
7446+
DiagnosticCode.Expression_resolves_to_unusual_type_0,
7447+
expression.range, this.currentType.toString()
7448+
);
74457449
return options.isWasm64
74467450
? module.i64(0)
74477451
: module.i32(0);
@@ -7778,38 +7782,10 @@ export class Compiler extends DiagnosticEmitter {
77787782
switch (expression.literalKind) {
77797783
case LiteralKind.ARRAY: {
77807784
assert(!implicitlyNegate);
7781-
let elementExpressions = (<ArrayLiteralExpression>expression).elementExpressions;
7782-
7783-
// Infer from first element in auto contexts
7784-
if (contextualType == Type.auto) {
7785-
return this.compileArrayLiteral(
7786-
Type.auto,
7787-
elementExpressions,
7788-
constraints,
7789-
expression
7790-
);
7791-
}
7792-
7793-
// Use contextual type if an array
7794-
if (contextualType.is(TypeFlags.REFERENCE)) {
7795-
let classType = contextualType.classReference;
7796-
if (classType) {
7797-
if (classType.prototype == this.program.arrayPrototype) {
7798-
return this.compileArrayLiteral(
7799-
assert(classType.typeArguments)[0],
7800-
elementExpressions,
7801-
constraints,
7802-
expression
7803-
);
7804-
}
7805-
}
7806-
}
7807-
7808-
this.error(
7809-
DiagnosticCode.The_type_argument_for_type_parameter_0_cannot_be_inferred_from_the_usage_Consider_specifying_the_type_arguments_explicitly,
7810-
expression.range, "T"
7785+
return this.compileArrayLiteral(
7786+
<ArrayLiteralExpression>expression,
7787+
constraints
78117788
);
7812-
return module.unreachable();
78137789
}
78147790
case LiteralKind.FLOAT: {
78157791
let floatValue = (<FloatLiteralExpression>expression).value;
@@ -7875,50 +7851,29 @@ export class Compiler extends DiagnosticEmitter {
78757851
}
78767852

78777853
private compileArrayLiteral(
7878-
elementType: Type,
7879-
expressions: (Expression | null)[],
7880-
constraints: Constraints,
7881-
reportNode: Node
7854+
expression: ArrayLiteralExpression,
7855+
constraints: Constraints
78827856
): ExpressionRef {
78837857
var module = this.module;
7858+
var flow = this.currentFlow;
7859+
7860+
var arrayInstance = this.resolver.lookupExpression(expression, flow, this.currentType);
7861+
if (!arrayInstance) return module.unreachable();
7862+
78847863
var program = this.program;
7885-
var arrayPrototype = assert(program.arrayPrototype);
78867864
var arrayBufferInstance = assert(program.arrayBufferInstance);
7887-
var flow = this.currentFlow;
78887865

78897866
// block those here so compiling expressions doesn't conflict
78907867
var tempThis = flow.getTempLocal(this.options.usizeType);
78917868
var tempDataStart = flow.getTempLocal(arrayBufferInstance.type);
78927869

7893-
// infer common element type in auto contexts
7894-
var length = expressions.length;
7895-
if (elementType == Type.auto) {
7896-
for (let i = 0; i < length; ++i) {
7897-
let expression = expressions[i];
7898-
if (expression) {
7899-
let currentType = this.resolver.resolveExpression(expression, this.currentFlow, elementType);
7900-
if (!currentType) return module.unreachable();
7901-
if (elementType == Type.auto) elementType = currentType;
7902-
else if (currentType != elementType) {
7903-
let commonType = Type.commonDenominator(elementType, currentType, false);
7904-
if (commonType) elementType = commonType;
7905-
// otherwise triggers error further down
7906-
}
7907-
}
7908-
}
7909-
if (elementType /* still */ == Type.auto) {
7910-
this.error(
7911-
DiagnosticCode.The_type_argument_for_type_parameter_0_cannot_be_inferred_from_the_usage_Consider_specifying_the_type_arguments_explicitly,
7912-
reportNode.range, "T"
7913-
);
7914-
return module.unreachable();
7915-
}
7916-
}
7917-
7918-
var arrayInstance = assert(this.resolver.resolveClass(arrayPrototype, [ elementType ]));
7919-
var arrayType = arrayInstance.type;
7870+
assert(arrayInstance.kind == ElementKind.CLASS);
7871+
var arrayType = (<Class>arrayInstance).type;
7872+
var elementType = assert((<Class>arrayInstance).getTypeArgumentsTo(this.program.arrayPrototype))[0];
7873+
var expressions = expression.elementExpressions;
79207874

79217875
// compile value expressions and find out whether all are constant
7876+
var length = expressions.length;
79227877
var values = new Array<ExpressionRef>(length);
79237878
var isStatic = true;
79247879
var nativeElementType = elementType.toNativeType();
@@ -7966,11 +7921,11 @@ export class Compiler extends DiagnosticEmitter {
79667921
program.options.isWasm64
79677922
? module.i64(elementType.alignLog2)
79687923
: module.i32(elementType.alignLog2),
7969-
module.i32(arrayInstance.id),
7924+
module.i32((<Class>arrayInstance).id),
79707925
program.options.isWasm64
79717926
? module.i64(i64_low(bufferAddress), i64_high(bufferAddress))
79727927
: module.i32(i64_low(bufferAddress))
7973-
], reportNode);
7928+
], expression);
79747929
this.currentType = arrayType;
79757930
expr = this.makeRetain(expr);
79767931
if (arrayType.isManaged) {
@@ -7985,13 +7940,13 @@ export class Compiler extends DiagnosticEmitter {
79857940
}
79867941

79877942
// otherwise compile an explicit instantiation with indexed sets
7988-
var setter = arrayInstance.lookupOverload(OperatorKind.INDEXED_SET, true);
7943+
var setter = (<Class>arrayInstance).lookupOverload(OperatorKind.INDEXED_SET, true);
79897944
if (!setter) {
79907945
flow.freeTempLocal(tempThis);
79917946
flow.freeTempLocal(tempDataStart);
79927947
this.error(
79937948
DiagnosticCode.Index_signature_in_type_0_only_permits_reading,
7994-
reportNode.range, arrayInstance.internalName
7949+
expression.range, arrayInstance.internalName
79957950
);
79967951
this.currentType = arrayType;
79977952
return module.unreachable();
@@ -8008,11 +7963,11 @@ export class Compiler extends DiagnosticEmitter {
80087963
program.options.isWasm64
80097964
? module.i64(elementType.alignLog2)
80107965
: module.i32(elementType.alignLog2),
8011-
module.i32(arrayInstance.id),
7966+
module.i32((<Class>arrayInstance).id),
80127967
program.options.isWasm64
80137968
? module.i64(0)
80147969
: module.i32(0)
8015-
], reportNode)
7970+
], expression)
80167971
)
80177972
)
80187973
);

src/diagnosticMessages.generated.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export enum DiagnosticCode {
3737
_0_must_be_a_power_of_two = 223,
3838
_0_is_not_a_valid_operator = 224,
3939
Expression_cannot_be_represented_by_a_type = 225,
40+
Expression_resolves_to_unusual_type_0 = 226,
4041
Type_0_is_cyclic_Module_will_include_deferred_garbage_collection = 900,
4142
Importing_the_table_disables_some_indirect_call_optimizations = 901,
4243
Exporting_the_table_disables_some_indirect_call_optimizations = 902,
@@ -187,6 +188,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
187188
case 223: return "'{0}' must be a power of two.";
188189
case 224: return "'{0}' is not a valid operator.";
189190
case 225: return "Expression cannot be represented by a type.";
191+
case 226: return "Expression resolves to unusual type '{0}'.";
190192
case 900: return "Type '{0}' is cyclic. Module will include deferred garbage collection.";
191193
case 901: return "Importing the table disables some indirect call optimizations.";
192194
case 902: return "Exporting the table disables some indirect call optimizations.";

src/diagnosticMessages.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"'{0}' must be a power of two.": 223,
3131
"'{0}' is not a valid operator.": 224,
3232
"Expression cannot be represented by a type.": 225,
33+
"Expression resolves to unusual type '{0}'.": 226,
3334

3435
"Type '{0}' is cyclic. Module will include deferred garbage collection.": 900,
3536
"Importing the table disables some indirect call optimizations.": 901,

src/resolver.ts

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ import {
6565
TernaryExpression,
6666
isTypeOmitted,
6767
FunctionExpression,
68-
NewExpression
68+
NewExpression,
69+
ArrayLiteralExpression
6970
} from "./ast";
7071

7172
import {
@@ -2160,10 +2161,10 @@ export class Resolver extends DiagnosticEmitter {
21602161
/** How to proceed with eventual diagnostics. */
21612162
reportMode: ReportMode = ReportMode.REPORT
21622163
): Element | null {
2164+
this.currentThisExpression = node;
2165+
this.currentElementExpression = null;
21632166
switch (node.literalKind) {
21642167
case LiteralKind.INTEGER: {
2165-
this.currentThisExpression = node;
2166-
this.currentElementExpression = null;
21672168
let intType = this.determineIntegerLiteralType(
21682169
(<IntegerLiteralExpression>node).value,
21692170
ctxType
@@ -2173,20 +2174,61 @@ export class Resolver extends DiagnosticEmitter {
21732174
return wrapperClasses.get(intType)!;
21742175
}
21752176
case LiteralKind.FLOAT: {
2176-
this.currentThisExpression = node;
2177-
this.currentElementExpression = null;
21782177
let fltType = ctxType == Type.f32 ? Type.f32 : Type.f64;
21792178
let wrapperClasses = this.program.wrapperClasses;
21802179
assert(wrapperClasses.has(fltType));
21812180
return wrapperClasses.get(fltType)!;
21822181
}
21832182
case LiteralKind.STRING: {
2184-
this.currentThisExpression = node;
2185-
this.currentElementExpression = null;
21862183
return this.program.stringInstance;
21872184
}
2188-
// TODO
2189-
// case LiteralKind.ARRAY:
2185+
case LiteralKind.ARRAY: {
2186+
let classReference = ctxType.classReference;
2187+
if (ctxType.is(TypeFlags.REFERENCE) && classReference !== null && classReference.prototype == this.program.arrayPrototype) {
2188+
return this.getElementOfType(ctxType);
2189+
}
2190+
// otherwise infer, ignoring ctxType
2191+
let expressions = (<ArrayLiteralExpression>node).elementExpressions;
2192+
let length = expressions.length;
2193+
let elementType = Type.auto;
2194+
let numNullLiterals = 0;
2195+
for (let i = 0, k = length; i < k; ++i) {
2196+
let expression = expressions[i];
2197+
if (expression) {
2198+
if (expression.kind == NodeKind.NULL && length > 1) {
2199+
++numNullLiterals;
2200+
} else {
2201+
let currentType = this.resolveExpression(expression, ctxFlow, elementType);
2202+
if (!currentType) return null;
2203+
if (elementType == Type.auto) elementType = currentType;
2204+
else if (currentType != elementType) {
2205+
let commonType = Type.commonDenominator(elementType, currentType, false);
2206+
if (commonType) elementType = commonType;
2207+
// otherwise triggers error on compilation
2208+
}
2209+
}
2210+
}
2211+
}
2212+
if (elementType /* still */ == Type.auto) {
2213+
if (numNullLiterals == length) { // all nulls infers as usize
2214+
elementType = this.program.options.usizeType;
2215+
} else {
2216+
this.error(
2217+
DiagnosticCode.The_type_argument_for_type_parameter_0_cannot_be_inferred_from_the_usage_Consider_specifying_the_type_arguments_explicitly,
2218+
node.range, "T"
2219+
);
2220+
return null;
2221+
}
2222+
}
2223+
if (
2224+
numNullLiterals > 0 &&
2225+
elementType.is(TypeFlags.REFERENCE) &&
2226+
!elementType.is(TypeFlags.HOST) // TODO: anyref isn't nullable as-is
2227+
) {
2228+
elementType = elementType.asNullable();
2229+
}
2230+
return assert(this.resolveClass(this.program.arrayPrototype, [ elementType ]));
2231+
}
21902232
}
21912233
if (reportMode == ReportMode.REPORT) {
21922234
this.error(

std/assembly/array.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class Array<T> extends ArrayBufferView {
3838
private length_: i32;
3939

4040
static isArray<U>(value: U): bool {
41-
return builtin_isArray(value) && value !== null;
41+
return isReference<U>() ? builtin_isArray(value) && value !== null : false;
4242
}
4343

4444
static create<T>(capacity: i32 = 0): Array<T> {
@@ -355,10 +355,7 @@ export class Array<T> extends ArrayBufferView {
355355
base + sizeof<T>(),
356356
<usize>lastIndex << alignof<T>()
357357
);
358-
store<T>(base + (<usize>lastIndex << alignof<T>()),
359-
// @ts-ignore: cast
360-
<T>null
361-
);
358+
store<T>(base + (<usize>lastIndex << alignof<T>()), isReference<T>() ? null : 0);
362359
this.length_ = lastIndex;
363360
return element; // no need to retain -> is moved
364361
}

tests/compiler/builtins.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,20 @@ assert(isInteger(<i32>1));
2424
assert(!isInteger(<f32>1));
2525
assert(isFloat(<f32>1));
2626
assert(!isFloat(<i32>1));
27-
assert(isReference(changetype<string>(null)));
28-
assert(!isReference(changetype<usize>(null)));
27+
assert(isReference(changetype<string>(0)));
28+
assert(!isReference(changetype<usize>(0)));
2929
assert(isString(""));
3030
assert(isString("abc"));
3131
assert(!isString(1));
32-
assert(isArray(changetype<i32[]>(null)));
33-
assert(isArrayLike(changetype<i32[]>(null)));
34-
assert(isArrayLike(changetype<string>(null)));
35-
assert(isArrayLike(changetype<Uint8Array>(null)));
36-
assert(!isArray(changetype<usize>(null)));
37-
assert(isFunction(changetype<() => void>(null)));
38-
assert(!isFunction(changetype<u32>(null)));
39-
assert(isNullable(changetype<C | null>(null)));
40-
assert(!isNullable(changetype<C>(null)));
32+
assert(isArray(changetype<i32[]>(0)));
33+
assert(isArrayLike(changetype<i32[]>(0)));
34+
assert(isArrayLike(changetype<string>(0)));
35+
assert(isArrayLike(changetype<Uint8Array>(0)));
36+
assert(!isArray(changetype<usize>(0)));
37+
assert(isFunction(changetype<() => void>(0)));
38+
assert(!isFunction(changetype<u32>(0)));
39+
assert(isNullable(changetype<C | null>(0)));
40+
assert(!isNullable(changetype<C>(0)));
4141

4242
// evaluation
4343

0 commit comments

Comments
 (0)