Skip to content

Commit a7b9244

Browse files
jtennerdcodeIO
authored andcommitted
Add various useful builtins (#739)
isVoid, lengthof, nameof, returnof and function type support for idof
1 parent 25b9276 commit a7b9244

18 files changed

+1150
-37
lines changed

src/builtins.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export namespace BuiltinSymbols {
102102
export const isDefined = "~lib/builtins/isDefined";
103103
export const isConstant = "~lib/builtins/isConstant";
104104
export const isManaged = "~lib/builtins/isManaged";
105+
export const isVoid = "~lib/builtins/isVoid";
105106

106107
export const clz = "~lib/builtins/clz";
107108
export const ctz = "~lib/builtins/ctz";
@@ -135,6 +136,8 @@ export namespace BuiltinSymbols {
135136
export const sizeof = "~lib/builtins/sizeof";
136137
export const alignof = "~lib/builtins/alignof";
137138
export const offsetof = "~lib/builtins/offsetof";
139+
export const nameof = "~lib/builtins/nameof";
140+
export const lengthof = "~lib/builtins/lengthof";
138141
export const select = "~lib/builtins/select";
139142
export const unreachable = "~lib/builtins/unreachable";
140143
export const changetype = "~lib/builtins/changetype";
@@ -658,6 +661,30 @@ export function compileCall(
658661
if (!type) return module.unreachable();
659662
return module.i32(type.isManaged ? 1 : 0);
660663
}
664+
case BuiltinSymbols.isVoid: { // isVoid<T>() -> bool
665+
let type = evaluateConstantType(compiler, typeArguments, operands, reportNode);
666+
compiler.currentType = Type.bool;
667+
if (!type) return module.unreachable();
668+
return module.i32(type.kind == TypeKind.VOID ? 1 : 0);
669+
}
670+
case BuiltinSymbols.lengthof: { // lengthof<T>(): i32
671+
let type = evaluateConstantType(compiler, typeArguments, operands, reportNode);
672+
compiler.currentType = Type.i32;
673+
if (!type) return module.unreachable();
674+
675+
// Report if there is no call signature
676+
let signatureReference = type.signatureReference;
677+
if (!signatureReference) {
678+
compiler.error(
679+
DiagnosticCode.Type_0_has_no_call_signatures,
680+
reportNode.range, "1", (typeArguments ? typeArguments.length : 1).toString(10)
681+
);
682+
return module.unreachable();
683+
}
684+
685+
let parameterNames = signatureReference.parameterNames;
686+
return module.i32(!parameterNames ? 0 : parameterNames.length);
687+
}
661688
case BuiltinSymbols.sizeof: { // sizeof<T!>() -> usize
662689
compiler.currentType = compiler.options.usizeType;
663690
if (
@@ -771,6 +798,45 @@ export function compileCall(
771798
}
772799
}
773800
}
801+
case BuiltinSymbols.nameof: {
802+
// Check to make sure a parameter or a type was passed to the builtin
803+
let resultType = evaluateConstantType(compiler, typeArguments, operands, reportNode);
804+
if (!resultType) return module.unreachable();
805+
806+
let value: string;
807+
if (resultType.is(TypeFlags.REFERENCE)) {
808+
let classReference = resultType.classReference;
809+
if (!classReference) {
810+
assert(resultType.signatureReference);
811+
value = "Function";
812+
} else {
813+
value = classReference.name;
814+
}
815+
} else {
816+
switch (resultType.kind) {
817+
case TypeKind.BOOL: { value = "bool"; break; }
818+
case TypeKind.I8: { value = "i8"; break; }
819+
case TypeKind.U8: { value = "u8"; break; }
820+
case TypeKind.I16: { value = "i16"; break; }
821+
case TypeKind.U16: { value = "u16"; break; }
822+
case TypeKind.I32: { value = "i32"; break; }
823+
case TypeKind.U32: { value = "u32"; break; }
824+
case TypeKind.F32: { value = "f32"; break; }
825+
case TypeKind.I64: { value = "i64"; break; }
826+
case TypeKind.U64: { value = "u64"; break; }
827+
case TypeKind.F64: { value = "f64"; break; }
828+
case TypeKind.ISIZE: { value = "isize"; break; }
829+
case TypeKind.USIZE: { value = "usize"; break; }
830+
case TypeKind.V128: { value = "v128"; break; }
831+
// If the kind is not set properly, throw an error.
832+
// The default case falls through to satisfy that value is always set, and never null.
833+
default: assert(false);
834+
case TypeKind.VOID: { value = "void"; break; }
835+
}
836+
}
837+
838+
return compiler.ensureStaticString(value);
839+
}
774840

775841
// === Math ===================================================================================
776842

@@ -3593,6 +3659,11 @@ export function compileCall(
35933659
let type = evaluateConstantType(compiler, typeArguments, operands, reportNode);
35943660
compiler.currentType = Type.u32;
35953661
if (!type) return module.unreachable();
3662+
let signatureReference = type.signatureReference;
3663+
if (type.is(TypeFlags.REFERENCE) && signatureReference !== null) {
3664+
return module.i32(signatureReference.id);
3665+
}
3666+
35963667
let classReference = type.classReference;
35973668
if (!classReference || classReference.hasDecorator(DecoratorFlags.UNMANAGED)) {
35983669
compiler.error(

src/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ export namespace CommonSymbols {
136136
export const native = "native";
137137
export const indexof = "indexof";
138138
export const valueof = "valueof";
139+
export const returnof = "returnof";
139140
// aliases
140141
export const null_ = "null";
141142
export const true_ = "true";

src/compiler.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ export class Compiler extends DiagnosticEmitter {
344344
program.initialize(options);
345345

346346
// set up the main start function
347-
var startFunctionInstance = program.makeNativeFunction("start", new Signature([], Type.void));
347+
var startFunctionInstance = program.makeNativeFunction("start", new Signature(program, [], Type.void));
348348
startFunctionInstance.internalName = "start";
349349
var startFunctionBody = new Array<ExpressionRef>();
350350
this.currentFlow = startFunctionInstance.flow;
@@ -6367,7 +6367,7 @@ export class Compiler extends DiagnosticEmitter {
63676367
assert(operandIndex == minOperands);
63686368

63696369
// create the trampoline element
6370-
var trampolineSignature = new Signature(originalParameterTypes, returnType, thisType);
6370+
var trampolineSignature = new Signature(this.program, originalParameterTypes, returnType, thisType);
63716371
trampolineSignature.requiredParameters = maxArguments;
63726372
trampolineSignature.parameterNames = originalSignature.parameterNames;
63736373
trampoline = new Function(
@@ -7082,7 +7082,7 @@ export class Compiler extends DiagnosticEmitter {
70827082
}
70837083
}
70847084

7085-
let signature = new Signature(parameterTypes, returnType, thisType);
7085+
let signature = new Signature(this.program, parameterTypes, returnType, thisType);
70867086
signature.requiredParameters = numParameters; // !
70877087
signature.parameterNames = parameterNames;
70887088
instance = new Function(
@@ -7845,7 +7845,7 @@ export class Compiler extends DiagnosticEmitter {
78457845
CommonFlags.INSTANCE | CommonFlags.CONSTRUCTOR
78467846
)
78477847
),
7848-
new Signature(null, classInstance.type, classInstance.type),
7848+
new Signature(this.program, null, classInstance.type, classInstance.type),
78497849
null
78507850
);
78517851
}
@@ -9005,6 +9005,7 @@ export class Compiler extends DiagnosticEmitter {
90059005
flow.popBreakLabel();
90069006
return module.block(label, conditions, NativeType.I32);
90079007
}
9008+
90089009
}
90099010

90109011
// helpers

src/diagnosticMessages.generated.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ export enum DiagnosticCode {
136136
Namespace_0_has_no_exported_member_1 = 2694,
137137
Required_type_parameters_may_not_follow_optional_type_parameters = 2706,
138138
Duplicate_property_0 = 2718,
139+
Type_0_has_no_call_signatures = 2757,
139140
File_0_not_found = 6054,
140141
Numeric_separators_are_not_allowed_here = 6188,
141142
Multiple_consecutive_numeric_separators_are_not_permitted = 6189,
@@ -275,6 +276,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
275276
case 2694: return "Namespace '{0}' has no exported member '{1}'.";
276277
case 2706: return "Required type parameters may not follow optional type parameters.";
277278
case 2718: return "Duplicate property '{0}'.";
279+
case 2757: return "Type '{0}' has no call signatures.";
278280
case 6054: return "File '{0}' not found.";
279281
case 6188: return "Numeric separators are not allowed here.";
280282
case 6189: return "Multiple consecutive numeric separators are not permitted.";

src/diagnosticMessages.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
"Namespace '{0}' has no exported member '{1}'.": 2694,
131131
"Required type parameters may not follow optional type parameters.": 2706,
132132
"Duplicate property '{0}'.": 2718,
133+
"Type '{0}' has no call signatures.": 2757,
133134

134135
"File '{0}' not found.": 6054,
135136
"Numeric separators are not allowed here.": 6188,

src/program.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,8 @@ export class Program extends DiagnosticEmitter {
336336
typeClasses: Map<TypeKind,Class> = new Map();
337337
/** Managed classes contained in the program, by id. */
338338
managedClasses: Map<i32,Class> = new Map();
339+
/** A set of unique function signatures contained in the program, by id. */
340+
uniqueSignatures: Signature[] = new Array<Signature>(0);
339341

340342
// standard references
341343

@@ -403,7 +405,8 @@ export class Program extends DiagnosticEmitter {
403405

404406
/** Next class id. */
405407
nextClassId: u32 = 0;
406-
408+
/** Next signature id. */
409+
nextSignatureId: i32 = 0;
407410
/** Constructs a new program, optionally inheriting parser diagnostics. */
408411
constructor(
409412
/** Shared array of diagnostic messages (emitted so far). */
@@ -579,6 +582,12 @@ export class Program extends DiagnosticEmitter {
579582
this.makeNativeTypeDeclaration(CommonSymbols.valueof, CommonFlags.EXPORT | CommonFlags.GENERIC),
580583
DecoratorFlags.BUILTIN
581584
));
585+
this.nativeFile.add(CommonSymbols.returnof, new TypeDefinition(
586+
CommonSymbols.returnof,
587+
this.nativeFile,
588+
this.makeNativeTypeDeclaration(CommonSymbols.returnof, CommonFlags.EXPORT | CommonFlags.GENERIC),
589+
DecoratorFlags.BUILTIN
590+
));
582591
if (options.hasFeature(Feature.SIMD)) this.registerNativeType(CommonSymbols.v128, Type.v128);
583592

584593
// register compiler hints
@@ -2141,7 +2150,7 @@ export class File extends Element {
21412150
program.filesByName.set(this.internalName, this);
21422151
var startFunction = this.program.makeNativeFunction(
21432152
"start:" + this.internalName,
2144-
new Signature(null, Type.void),
2153+
new Signature(program, null, Type.void),
21452154
this
21462155
);
21472156
startFunction.internalName = startFunction.name;

src/resolver.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ export class Resolver extends DiagnosticEmitter {
273273
case CommonSymbols.native: return this.resolveBuiltinNativeType(node, ctxElement, ctxTypes, reportMode);
274274
case CommonSymbols.indexof: return this.resolveBuiltinIndexofType(node, ctxElement, ctxTypes, reportMode);
275275
case CommonSymbols.valueof: return this.resolveBuiltinValueofType(node, ctxElement, ctxTypes, reportMode);
276+
case CommonSymbols.returnof: return this.resolveBuiltinReturnTypeType(node, ctxElement, ctxTypes, reportMode);
276277
}
277278
}
278279

@@ -405,7 +406,7 @@ export class Resolver extends DiagnosticEmitter {
405406
);
406407
if (!returnType) return null;
407408
}
408-
var signature = new Signature(parameterTypes, returnType, thisType);
409+
var signature = new Signature(this.program, parameterTypes, returnType, thisType);
409410
signature.parameterNames = parameterNames;
410411
signature.requiredParameters = requiredParameters;
411412
signature.hasRest = hasRest;
@@ -550,6 +551,41 @@ export class Resolver extends DiagnosticEmitter {
550551
return null;
551552
}
552553

554+
private resolveBuiltinReturnTypeType(
555+
/** The type to resolve. */
556+
node: NamedTypeNode,
557+
/** Contextual element. */
558+
ctxElement: Element,
559+
/** Contextual types, i.e. `T`. */
560+
ctxTypes: Map<string,Type> | null = null,
561+
/** How to proceed with eventualy diagnostics. */
562+
reportMode: ReportMode = ReportMode.REPORT
563+
): Type | null {
564+
var typeArgumentNodes = node.typeArguments;
565+
if (!(typeArgumentNodes && typeArgumentNodes.length == 1)) {
566+
if (reportMode == ReportMode.REPORT) {
567+
this.error(
568+
DiagnosticCode.Expected_0_type_arguments_but_got_1,
569+
node.range, "1", (typeArgumentNodes ? typeArgumentNodes.length : 1).toString(10)
570+
);
571+
}
572+
return null;
573+
}
574+
var typeArgument = this.resolveType(typeArgumentNodes[0], ctxElement, ctxTypes, reportMode);
575+
if (!typeArgument) return null;
576+
var signatureReference = typeArgument.signatureReference;
577+
if (!signatureReference) {
578+
if (reportMode == ReportMode.REPORT) {
579+
this.error(
580+
DiagnosticCode.Type_0_has_no_call_signatures,
581+
typeArgumentNodes[0].range, typeArgument.toString()
582+
);
583+
}
584+
return null;
585+
}
586+
return signatureReference.returnType;
587+
}
588+
553589
/** Resolves a type name to the program element it refers to. */
554590
resolveTypeName(
555591
/** The type name to resolve. */
@@ -1551,7 +1587,7 @@ export class Resolver extends DiagnosticEmitter {
15511587
returnType = type;
15521588
}
15531589

1554-
var signature = new Signature(parameterTypes, returnType, thisType);
1590+
var signature = new Signature(this.program, parameterTypes, returnType, thisType);
15551591
signature.parameterNames = parameterNames;
15561592
signature.requiredParameters = requiredParameters;
15571593

src/types.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,8 @@ export function typesToString(types: Type[]): string {
556556

557557
/** Represents a fully resolved function signature. */
558558
export class Signature {
559-
559+
/** The unique program id that represents this signature. */
560+
id: u32 = 0;
560561
/** Parameter types, if any, excluding `this`. */
561562
parameterTypes: Type[];
562563
/** Parameter names, if known, excluding `this`. */
@@ -573,9 +574,12 @@ export class Signature {
573574
cachedFunctionTarget: FunctionTarget | null = null;
574575
/** Respective function type. */
575576
type: Type;
577+
/** The program that created this signature. */
578+
program: Program;
576579

577580
/** Constructs a new signature. */
578581
constructor(
582+
program: Program,
579583
parameterTypes: Type[] | null = null,
580584
returnType: Type | null = null,
581585
thisType: Type | null = null
@@ -585,8 +589,21 @@ export class Signature {
585589
this.requiredParameters = 0;
586590
this.returnType = returnType ? returnType : Type.void;
587591
this.thisType = thisType;
592+
this.program = program;
588593
this.hasRest = false;
589594
this.type = Type.u32.asFunction(this);
595+
596+
var signatureTypes = program.uniqueSignatures;
597+
var length = signatureTypes.length;
598+
for (let i = 0; i < length; i++) {
599+
let compare = signatureTypes[i];
600+
if (this.equals(compare)) {
601+
this.id = compare.id;
602+
return this;
603+
}
604+
}
605+
program.uniqueSignatures.push(this);
606+
this.id = program.nextSignatureId++;
590607
}
591608

592609
asFunctionTarget(program: Program): FunctionTarget {
@@ -606,23 +623,28 @@ export class Signature {
606623

607624
/** Tests if a value of this function type is assignable to a target of the specified function type. */
608625
isAssignableTo(target: Signature): bool {
626+
return this.equals(target);
627+
}
628+
629+
/** Tests to see if a signature equals another signature. */
630+
equals(value: Signature): bool {
609631
// TODO: maybe cache results?
610632

611633
// check `this` type
612634
var thisThisType = this.thisType;
613-
var targetThisType = target.thisType;
635+
var targetThisType = value.thisType;
614636
if (thisThisType) {
615637
if (!(targetThisType && thisThisType.isAssignableTo(targetThisType))) return false;
616638
} else if (targetThisType) {
617639
return false;
618640
}
619641

620642
// check rest parameter
621-
if (this.hasRest != target.hasRest) return false; // TODO
643+
if (this.hasRest != value.hasRest) return false; // TODO
622644

623645
// check parameter types
624646
var thisParameterTypes = this.parameterTypes;
625-
var targetParameterTypes = target.parameterTypes;
647+
var targetParameterTypes = value.parameterTypes;
626648
var numParameters = thisParameterTypes.length;
627649
if (numParameters != targetParameterTypes.length) return false;
628650
for (let i = 0; i < numParameters; ++i) {
@@ -633,7 +655,7 @@ export class Signature {
633655

634656
// check return type
635657
var thisReturnType = this.returnType;
636-
var targetReturnType = target.returnType;
658+
var targetReturnType = value.returnType;
637659
return thisReturnType == targetReturnType || thisReturnType.isAssignableTo(targetReturnType);
638660
}
639661

0 commit comments

Comments
 (0)