diff --git a/src/common.ts b/src/common.ts index 31912656..5ddef236 100644 --- a/src/common.ts +++ b/src/common.ts @@ -134,6 +134,7 @@ export namespace CommonSymbols { export const boolean = "boolean"; export const string = "string"; export const native = "native"; + export const valueof = "valueof"; // aliases export const null_ = "null"; export const true_ = "true"; diff --git a/src/program.ts b/src/program.ts index 683fd2f6..f354e347 100644 --- a/src/program.ts +++ b/src/program.ts @@ -567,6 +567,12 @@ export class Program extends DiagnosticEmitter { this.makeNativeTypeDeclaration(CommonSymbols.native, CommonFlags.EXPORT | CommonFlags.GENERIC), DecoratorFlags.BUILTIN )); + this.nativeFile.add(CommonSymbols.valueof, new TypeDefinition( + CommonSymbols.valueof, + this.nativeFile, + this.makeNativeTypeDeclaration(CommonSymbols.valueof, CommonFlags.EXPORT | CommonFlags.GENERIC), + DecoratorFlags.BUILTIN + )); if (options.hasFeature(Feature.SIMD)) this.registerNativeType(CommonSymbols.v128, Type.v128); // register compiler hints diff --git a/src/resolver.ts b/src/resolver.ts index cdc7857e..9ccb32cb 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -212,10 +212,10 @@ export class Resolver extends DiagnosticEmitter { var typeNode = node; var typeName = typeNode.name; var typeArgumentNodes = typeNode.typeArguments; - var possiblyPlaceholder = !typeName.next; + var isSimpleType = !typeName.next; - // look up in contextual type arguments if possibly a placeholder - if (possiblyPlaceholder) { + // look up in contextual type arguments if a simple type + if (isSimpleType) { if (contextualTypeArguments && contextualTypeArguments.has(typeName.identifier.text)) { let type = contextualTypeArguments.get(typeName.identifier.text)!; if (typeArgumentNodes !== null && typeArgumentNodes.length) { @@ -314,41 +314,11 @@ export class Resolver extends DiagnosticEmitter { return type; } - // handle special native type - if (possiblyPlaceholder && typeName.identifier.text == CommonSymbols.native) { - if (!(typeArgumentNodes && typeArgumentNodes.length == 1)) { - if (reportMode == ReportMode.REPORT) { - this.error( - DiagnosticCode.Expected_0_type_arguments_but_got_1, - typeNode.range, "1", (typeArgumentNodes ? typeArgumentNodes.length : 1).toString(10) - ); - } - return null; - } - let typeArgument = this.resolveType( - typeArgumentNodes[0], - context, - contextualTypeArguments, - reportMode - ); - if (!typeArgument) return null; - switch (typeArgument.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.I32: return Type.i32; - case TypeKind.ISIZE: if (!this.program.options.isWasm64) return Type.i32; - case TypeKind.I64: return Type.i64; - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.U32: - case TypeKind.BOOL: return Type.u32; - case TypeKind.USIZE: if (!this.program.options.isWasm64) return Type.u32; - case TypeKind.U64: return Type.u64; - case TypeKind.F32: return Type.f32; - case TypeKind.F64: return Type.f64; - case TypeKind.V128: return Type.v128; - case TypeKind.VOID: return Type.void; - default: assert(false); + // handle built-in types + if (isSimpleType) { + switch (typeName.identifier.symbol) { + case CommonSymbols.native: return this.resolveBuiltinNativeType(typeNode, context, contextualTypeArguments, reportMode); + case CommonSymbols.valueof: return this.resolveBuiltinValueofType(typeNode, context, contextualTypeArguments, reportMode) } } @@ -402,6 +372,110 @@ export class Resolver extends DiagnosticEmitter { return null; } + private resolveBuiltinNativeType( + /** The type to resolve. */ + typeNode: TypeNode, + /** Relative context. */ + context: Element, + /** Type arguments inherited through context, i.e. `T`. */ + contextualTypeArguments: Map | null = null, + /** How to proceed with eventualy diagnostics. */ + reportMode: ReportMode = ReportMode.REPORT + ): Type | null { + var typeArgumentNodes = typeNode.typeArguments; + if (!(typeArgumentNodes && typeArgumentNodes.length == 1)) { + if (reportMode == ReportMode.REPORT) { + this.error( + DiagnosticCode.Expected_0_type_arguments_but_got_1, + typeNode.range, "1", (typeArgumentNodes ? typeArgumentNodes.length : 1).toString(10) + ); + } + return null; + } + var typeArgument = this.resolveType(typeArgumentNodes[0], context, contextualTypeArguments, reportMode); + if (!typeArgument) return null; + switch (typeArgument.kind) { + case TypeKind.I8: + case TypeKind.I16: + case TypeKind.I32: return Type.i32; + case TypeKind.ISIZE: if (!this.program.options.isWasm64) return Type.i32; + case TypeKind.I64: return Type.i64; + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.U32: + case TypeKind.BOOL: return Type.u32; + case TypeKind.USIZE: if (!this.program.options.isWasm64) return Type.u32; + case TypeKind.U64: return Type.u64; + case TypeKind.F32: return Type.f32; + case TypeKind.F64: return Type.f64; + case TypeKind.V128: return Type.v128; + case TypeKind.VOID: return Type.void; + default: assert(false); + } + return null; + } + + private resolveBuiltinValueofType( + /** The type to resolve. */ + typeNode: TypeNode, + /** Relative context. */ + context: Element, + /** Type arguments inherited through context, i.e. `T`. */ + contextualTypeArguments: Map | null = null, + /** How to proceed with eventualy diagnostics. */ + reportMode: ReportMode = ReportMode.REPORT + ): Type | null { + var typeArgumentNodes = typeNode.typeArguments; + if (!(typeArgumentNodes && typeArgumentNodes.length == 1)) { + if (reportMode == ReportMode.REPORT) { + this.error( + DiagnosticCode.Expected_0_type_arguments_but_got_1, + typeNode.range, "1", (typeArgumentNodes ? typeArgumentNodes.length : 1).toString(10) + ); + } + return null; + } + var typeArgument = this.resolveType(typeArgumentNodes[0], context, contextualTypeArguments, reportMode); + if (!typeArgument) return null; + var classReference = typeArgument.classReference; + if (!classReference) { + if (reportMode == ReportMode.REPORT) { + this.error( + DiagnosticCode.Index_signature_is_missing_in_type_0, + typeArgumentNodes[0].range, typeArgument.toString() + ); + } + return null; + } + var program = this.program; + var mapPrototype = program.mapPrototype; + var setPrototype = program.setPrototype; + var arrayPrototype = program.arrayPrototype; + if (classReference.extends(arrayPrototype)) { + let actualTypeArguments = assert(classReference.getTypeArgumentsTo(arrayPrototype)); + assert(actualTypeArguments.length == 1); + return actualTypeArguments[0]; + } else if (classReference.extends(mapPrototype)) { + let actualTypeArguments = assert(classReference.getTypeArgumentsTo(arrayPrototype)); + assert(actualTypeArguments.length == 2); + return actualTypeArguments[1]; + } else if (classReference.extends(setPrototype)) { + let actualTypeArguments = assert(classReference.getTypeArgumentsTo(arrayPrototype)); + assert(actualTypeArguments.length == 1); + return actualTypeArguments[0]; + } else { + let overload = classReference.lookupOverload(OperatorKind.INDEXED_GET); + if (overload) return overload.signature.returnType; + if (reportMode == ReportMode.REPORT) { + this.error( + DiagnosticCode.Index_signature_is_missing_in_type_0, + typeArgumentNodes[0].range, typeArgument.toString() + ); + } + return null; + } + } + /** Resolves a type name to the program element it refers to. */ resolveTypeName( /** The type name to resolve. */ diff --git a/std/assembly/index.d.ts b/std/assembly/index.d.ts index 02b7ccde..71c57a2a 100644 --- a/std/assembly/index.d.ts +++ b/std/assembly/index.d.ts @@ -882,6 +882,8 @@ declare namespace v8x16 { } /** Macro type evaluating to the underlying native WebAssembly type. */ declare type native = T; +/** Special type evaluating the value type of a collection. */ +declare type valueof = T[0]; /** Pseudo-class representing the backing class of integer types. */ declare class _Integer { diff --git a/std/assembly/typedarray.ts b/std/assembly/typedarray.ts index 3965de71..493dc6d2 100644 --- a/std/assembly/typedarray.ts +++ b/std/assembly/typedarray.ts @@ -1068,7 +1068,7 @@ function FOREACH( // @ts-ignore: decorator @inline -export function REVERSE(array: TArray): TArray { +function REVERSE(array: TArray): TArray { var dataStart = array.dataStart; for (let front = 0, back = array.length - 1; front < back; ++front, --back) { let frontPtr = dataStart + (front << alignof()); diff --git a/std/portable/index.d.ts b/std/portable/index.d.ts index ab049ee3..67552f8d 100644 --- a/std/portable/index.d.ts +++ b/std/portable/index.d.ts @@ -28,6 +28,9 @@ declare type usize = number; declare type f32 = number; declare type f64 = number; +/** Special type evaluating the value type of a collection. */ +declare type valueof = T[0]; + // Compiler hints /** Compiler target. 0 = JS, 1 = WASM32, 2 = WASM64. */ diff --git a/tests/compiler/valueof.json b/tests/compiler/valueof.json new file mode 100644 index 00000000..b1da366f --- /dev/null +++ b/tests/compiler/valueof.json @@ -0,0 +1,5 @@ +{ + "asc_flags": [ + "--runtime none" + ] +} \ No newline at end of file diff --git a/tests/compiler/valueof.optimized.wat b/tests/compiler/valueof.optimized.wat new file mode 100644 index 00000000..e396f28a --- /dev/null +++ b/tests/compiler/valueof.optimized.wat @@ -0,0 +1,9 @@ +(module + (type $FUNCSIG$v (func)) + (memory $0 1) + (data (i32.const 8) "\14\00\00\00\01\00\00\00\01\00\00\00\14\00\00\00v\00a\00l\00u\00e\00o\00f\00.\00t\00s") + (export "memory" (memory $0)) + (func $start (; 0 ;) (type $FUNCSIG$v) + nop + ) +) diff --git a/tests/compiler/valueof.ts b/tests/compiler/valueof.ts new file mode 100644 index 00000000..ee200c22 --- /dev/null +++ b/tests/compiler/valueof.ts @@ -0,0 +1,26 @@ +// simple +assert(isInteger>()); +assert(isSigned>()); +assert(sizeof>() == 1); + +// alias +type u32Array = u32[]; +assert(isInteger>()); +assert(!isSigned>()); +assert(sizeof>() == 4); + +// float +assert(isFloat>()); +assert(sizeof>() == 4); + +// string +assert(isString>()); +assert(isManaged>()); + +// array +assert(isArray>()); + +// typed array +assert(isInteger>()); +assert(!isSigned>()); +assert(sizeof>() == 1); diff --git a/tests/compiler/valueof.untouched.wat b/tests/compiler/valueof.untouched.wat new file mode 100644 index 00000000..43cb6cdc --- /dev/null +++ b/tests/compiler/valueof.untouched.wat @@ -0,0 +1,168 @@ +(module + (type $FUNCSIG$viiii (func (param i32 i32 i32 i32))) + (type $FUNCSIG$v (func)) + (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) + (memory $0 1) + (data (i32.const 8) "\14\00\00\00\01\00\00\00\01\00\00\00\14\00\00\00v\00a\00l\00u\00e\00o\00f\00.\00t\00s\00") + (table $0 1 funcref) + (elem (i32.const 0) $null) + (export "memory" (memory $0)) + (start $start) + (func $start:valueof (; 1 ;) (type $FUNCSIG$v) + i32.const 1 + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 2 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + i32.const 1 + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 3 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + i32.const 1 + i32.const 1 + i32.eq + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 4 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + i32.const 1 + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 8 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + i32.const 0 + i32.eqz + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 9 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + i32.const 4 + i32.const 4 + i32.eq + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 10 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + i32.const 1 + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 13 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + i32.const 4 + i32.const 4 + i32.eq + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 14 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + i32.const 1 + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 17 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + i32.const 1 + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 18 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + i32.const 1 + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 21 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + i32.const 1 + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 24 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + i32.const 0 + i32.eqz + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 25 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + i32.const 1 + i32.const 1 + i32.eq + i32.eqz + if + i32.const 0 + i32.const 24 + i32.const 26 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + ) + (func $start (; 2 ;) (type $FUNCSIG$v) + call $start:valueof + ) + (func $null (; 3 ;) (type $FUNCSIG$v) + ) +)