Skip to content

Commit

Permalink
Add various useful builtins (#739)
Browse files Browse the repository at this point in the history
isVoid, lengthof, nameof, returnof and function type support for idof
  • Loading branch information
jtenner authored and dcodeIO committed Aug 13, 2019
1 parent 25b9276 commit a7b9244
Show file tree
Hide file tree
Showing 18 changed files with 1,150 additions and 37 deletions.
71 changes: 71 additions & 0 deletions src/builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export namespace BuiltinSymbols {
export const isDefined = "~lib/builtins/isDefined";
export const isConstant = "~lib/builtins/isConstant";
export const isManaged = "~lib/builtins/isManaged";
export const isVoid = "~lib/builtins/isVoid";

export const clz = "~lib/builtins/clz";
export const ctz = "~lib/builtins/ctz";
Expand Down Expand Up @@ -135,6 +136,8 @@ export namespace BuiltinSymbols {
export const sizeof = "~lib/builtins/sizeof";
export const alignof = "~lib/builtins/alignof";
export const offsetof = "~lib/builtins/offsetof";
export const nameof = "~lib/builtins/nameof";
export const lengthof = "~lib/builtins/lengthof";
export const select = "~lib/builtins/select";
export const unreachable = "~lib/builtins/unreachable";
export const changetype = "~lib/builtins/changetype";
Expand Down Expand Up @@ -658,6 +661,30 @@ export function compileCall(
if (!type) return module.unreachable();
return module.i32(type.isManaged ? 1 : 0);
}
case BuiltinSymbols.isVoid: { // isVoid<T>() -> bool
let type = evaluateConstantType(compiler, typeArguments, operands, reportNode);
compiler.currentType = Type.bool;
if (!type) return module.unreachable();
return module.i32(type.kind == TypeKind.VOID ? 1 : 0);
}
case BuiltinSymbols.lengthof: { // lengthof<T>(): i32
let type = evaluateConstantType(compiler, typeArguments, operands, reportNode);
compiler.currentType = Type.i32;
if (!type) return module.unreachable();

// Report if there is no call signature
let signatureReference = type.signatureReference;
if (!signatureReference) {
compiler.error(
DiagnosticCode.Type_0_has_no_call_signatures,
reportNode.range, "1", (typeArguments ? typeArguments.length : 1).toString(10)
);
return module.unreachable();
}

let parameterNames = signatureReference.parameterNames;
return module.i32(!parameterNames ? 0 : parameterNames.length);
}
case BuiltinSymbols.sizeof: { // sizeof<T!>() -> usize
compiler.currentType = compiler.options.usizeType;
if (
Expand Down Expand Up @@ -771,6 +798,45 @@ export function compileCall(
}
}
}
case BuiltinSymbols.nameof: {
// Check to make sure a parameter or a type was passed to the builtin
let resultType = evaluateConstantType(compiler, typeArguments, operands, reportNode);
if (!resultType) return module.unreachable();

let value: string;
if (resultType.is(TypeFlags.REFERENCE)) {
let classReference = resultType.classReference;
if (!classReference) {
assert(resultType.signatureReference);
value = "Function";
} else {
value = classReference.name;
}
} else {
switch (resultType.kind) {
case TypeKind.BOOL: { value = "bool"; break; }
case TypeKind.I8: { value = "i8"; break; }
case TypeKind.U8: { value = "u8"; break; }
case TypeKind.I16: { value = "i16"; break; }
case TypeKind.U16: { value = "u16"; break; }
case TypeKind.I32: { value = "i32"; break; }
case TypeKind.U32: { value = "u32"; break; }
case TypeKind.F32: { value = "f32"; break; }
case TypeKind.I64: { value = "i64"; break; }
case TypeKind.U64: { value = "u64"; break; }
case TypeKind.F64: { value = "f64"; break; }
case TypeKind.ISIZE: { value = "isize"; break; }
case TypeKind.USIZE: { value = "usize"; break; }
case TypeKind.V128: { value = "v128"; break; }
// If the kind is not set properly, throw an error.
// The default case falls through to satisfy that value is always set, and never null.
default: assert(false);
case TypeKind.VOID: { value = "void"; break; }
}
}

return compiler.ensureStaticString(value);
}

// === Math ===================================================================================

Expand Down Expand Up @@ -3593,6 +3659,11 @@ export function compileCall(
let type = evaluateConstantType(compiler, typeArguments, operands, reportNode);
compiler.currentType = Type.u32;
if (!type) return module.unreachable();
let signatureReference = type.signatureReference;
if (type.is(TypeFlags.REFERENCE) && signatureReference !== null) {
return module.i32(signatureReference.id);
}

let classReference = type.classReference;
if (!classReference || classReference.hasDecorator(DecoratorFlags.UNMANAGED)) {
compiler.error(
Expand Down
1 change: 1 addition & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export namespace CommonSymbols {
export const native = "native";
export const indexof = "indexof";
export const valueof = "valueof";
export const returnof = "returnof";
// aliases
export const null_ = "null";
export const true_ = "true";
Expand Down
9 changes: 5 additions & 4 deletions src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ export class Compiler extends DiagnosticEmitter {
program.initialize(options);

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

// create the trampoline element
var trampolineSignature = new Signature(originalParameterTypes, returnType, thisType);
var trampolineSignature = new Signature(this.program, originalParameterTypes, returnType, thisType);
trampolineSignature.requiredParameters = maxArguments;
trampolineSignature.parameterNames = originalSignature.parameterNames;
trampoline = new Function(
Expand Down Expand Up @@ -7082,7 +7082,7 @@ export class Compiler extends DiagnosticEmitter {
}
}

let signature = new Signature(parameterTypes, returnType, thisType);
let signature = new Signature(this.program, parameterTypes, returnType, thisType);
signature.requiredParameters = numParameters; // !
signature.parameterNames = parameterNames;
instance = new Function(
Expand Down Expand Up @@ -7845,7 +7845,7 @@ export class Compiler extends DiagnosticEmitter {
CommonFlags.INSTANCE | CommonFlags.CONSTRUCTOR
)
),
new Signature(null, classInstance.type, classInstance.type),
new Signature(this.program, null, classInstance.type, classInstance.type),
null
);
}
Expand Down Expand Up @@ -9005,6 +9005,7 @@ export class Compiler extends DiagnosticEmitter {
flow.popBreakLabel();
return module.block(label, conditions, NativeType.I32);
}

}

// helpers
Expand Down
2 changes: 2 additions & 0 deletions src/diagnosticMessages.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export enum DiagnosticCode {
Namespace_0_has_no_exported_member_1 = 2694,
Required_type_parameters_may_not_follow_optional_type_parameters = 2706,
Duplicate_property_0 = 2718,
Type_0_has_no_call_signatures = 2757,
File_0_not_found = 6054,
Numeric_separators_are_not_allowed_here = 6188,
Multiple_consecutive_numeric_separators_are_not_permitted = 6189,
Expand Down Expand Up @@ -275,6 +276,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
case 2694: return "Namespace '{0}' has no exported member '{1}'.";
case 2706: return "Required type parameters may not follow optional type parameters.";
case 2718: return "Duplicate property '{0}'.";
case 2757: return "Type '{0}' has no call signatures.";
case 6054: return "File '{0}' not found.";
case 6188: return "Numeric separators are not allowed here.";
case 6189: return "Multiple consecutive numeric separators are not permitted.";
Expand Down
1 change: 1 addition & 0 deletions src/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
"Namespace '{0}' has no exported member '{1}'.": 2694,
"Required type parameters may not follow optional type parameters.": 2706,
"Duplicate property '{0}'.": 2718,
"Type '{0}' has no call signatures.": 2757,

"File '{0}' not found.": 6054,
"Numeric separators are not allowed here.": 6188,
Expand Down
13 changes: 11 additions & 2 deletions src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,8 @@ export class Program extends DiagnosticEmitter {
typeClasses: Map<TypeKind,Class> = new Map();
/** Managed classes contained in the program, by id. */
managedClasses: Map<i32,Class> = new Map();
/** A set of unique function signatures contained in the program, by id. */
uniqueSignatures: Signature[] = new Array<Signature>(0);

// standard references

Expand Down Expand Up @@ -403,7 +405,8 @@ export class Program extends DiagnosticEmitter {

/** Next class id. */
nextClassId: u32 = 0;

/** Next signature id. */
nextSignatureId: i32 = 0;
/** Constructs a new program, optionally inheriting parser diagnostics. */
constructor(
/** Shared array of diagnostic messages (emitted so far). */
Expand Down Expand Up @@ -579,6 +582,12 @@ export class Program extends DiagnosticEmitter {
this.makeNativeTypeDeclaration(CommonSymbols.valueof, CommonFlags.EXPORT | CommonFlags.GENERIC),
DecoratorFlags.BUILTIN
));
this.nativeFile.add(CommonSymbols.returnof, new TypeDefinition(
CommonSymbols.returnof,
this.nativeFile,
this.makeNativeTypeDeclaration(CommonSymbols.returnof, CommonFlags.EXPORT | CommonFlags.GENERIC),
DecoratorFlags.BUILTIN
));
if (options.hasFeature(Feature.SIMD)) this.registerNativeType(CommonSymbols.v128, Type.v128);

// register compiler hints
Expand Down Expand Up @@ -2141,7 +2150,7 @@ export class File extends Element {
program.filesByName.set(this.internalName, this);
var startFunction = this.program.makeNativeFunction(
"start:" + this.internalName,
new Signature(null, Type.void),
new Signature(program, null, Type.void),
this
);
startFunction.internalName = startFunction.name;
Expand Down
40 changes: 38 additions & 2 deletions src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ export class Resolver extends DiagnosticEmitter {
case CommonSymbols.native: return this.resolveBuiltinNativeType(node, ctxElement, ctxTypes, reportMode);
case CommonSymbols.indexof: return this.resolveBuiltinIndexofType(node, ctxElement, ctxTypes, reportMode);
case CommonSymbols.valueof: return this.resolveBuiltinValueofType(node, ctxElement, ctxTypes, reportMode);
case CommonSymbols.returnof: return this.resolveBuiltinReturnTypeType(node, ctxElement, ctxTypes, reportMode);
}
}

Expand Down Expand Up @@ -405,7 +406,7 @@ export class Resolver extends DiagnosticEmitter {
);
if (!returnType) return null;
}
var signature = new Signature(parameterTypes, returnType, thisType);
var signature = new Signature(this.program, parameterTypes, returnType, thisType);
signature.parameterNames = parameterNames;
signature.requiredParameters = requiredParameters;
signature.hasRest = hasRest;
Expand Down Expand Up @@ -550,6 +551,41 @@ export class Resolver extends DiagnosticEmitter {
return null;
}

private resolveBuiltinReturnTypeType(
/** The type to resolve. */
node: NamedTypeNode,
/** Contextual element. */
ctxElement: Element,
/** Contextual types, i.e. `T`. */
ctxTypes: Map<string,Type> | null = null,
/** How to proceed with eventualy diagnostics. */
reportMode: ReportMode = ReportMode.REPORT
): Type | null {
var typeArgumentNodes = node.typeArguments;
if (!(typeArgumentNodes && typeArgumentNodes.length == 1)) {
if (reportMode == ReportMode.REPORT) {
this.error(
DiagnosticCode.Expected_0_type_arguments_but_got_1,
node.range, "1", (typeArgumentNodes ? typeArgumentNodes.length : 1).toString(10)
);
}
return null;
}
var typeArgument = this.resolveType(typeArgumentNodes[0], ctxElement, ctxTypes, reportMode);
if (!typeArgument) return null;
var signatureReference = typeArgument.signatureReference;
if (!signatureReference) {
if (reportMode == ReportMode.REPORT) {
this.error(
DiagnosticCode.Type_0_has_no_call_signatures,
typeArgumentNodes[0].range, typeArgument.toString()
);
}
return null;
}
return signatureReference.returnType;
}

/** Resolves a type name to the program element it refers to. */
resolveTypeName(
/** The type name to resolve. */
Expand Down Expand Up @@ -1551,7 +1587,7 @@ export class Resolver extends DiagnosticEmitter {
returnType = type;
}

var signature = new Signature(parameterTypes, returnType, thisType);
var signature = new Signature(this.program, parameterTypes, returnType, thisType);
signature.parameterNames = parameterNames;
signature.requiredParameters = requiredParameters;

Expand Down
32 changes: 27 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,8 @@ export function typesToString(types: Type[]): string {

/** Represents a fully resolved function signature. */
export class Signature {

/** The unique program id that represents this signature. */
id: u32 = 0;
/** Parameter types, if any, excluding `this`. */
parameterTypes: Type[];
/** Parameter names, if known, excluding `this`. */
Expand All @@ -573,9 +574,12 @@ export class Signature {
cachedFunctionTarget: FunctionTarget | null = null;
/** Respective function type. */
type: Type;
/** The program that created this signature. */
program: Program;

/** Constructs a new signature. */
constructor(
program: Program,
parameterTypes: Type[] | null = null,
returnType: Type | null = null,
thisType: Type | null = null
Expand All @@ -585,8 +589,21 @@ export class Signature {
this.requiredParameters = 0;
this.returnType = returnType ? returnType : Type.void;
this.thisType = thisType;
this.program = program;
this.hasRest = false;
this.type = Type.u32.asFunction(this);

var signatureTypes = program.uniqueSignatures;
var length = signatureTypes.length;
for (let i = 0; i < length; i++) {
let compare = signatureTypes[i];
if (this.equals(compare)) {
this.id = compare.id;
return this;
}
}
program.uniqueSignatures.push(this);
this.id = program.nextSignatureId++;
}

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

/** Tests if a value of this function type is assignable to a target of the specified function type. */
isAssignableTo(target: Signature): bool {
return this.equals(target);
}

/** Tests to see if a signature equals another signature. */
equals(value: Signature): bool {
// TODO: maybe cache results?

// check `this` type
var thisThisType = this.thisType;
var targetThisType = target.thisType;
var targetThisType = value.thisType;
if (thisThisType) {
if (!(targetThisType && thisThisType.isAssignableTo(targetThisType))) return false;
} else if (targetThisType) {
return false;
}

// check rest parameter
if (this.hasRest != target.hasRest) return false; // TODO
if (this.hasRest != value.hasRest) return false; // TODO

// check parameter types
var thisParameterTypes = this.parameterTypes;
var targetParameterTypes = target.parameterTypes;
var targetParameterTypes = value.parameterTypes;
var numParameters = thisParameterTypes.length;
if (numParameters != targetParameterTypes.length) return false;
for (let i = 0; i < numParameters; ++i) {
Expand All @@ -633,7 +655,7 @@ export class Signature {

// check return type
var thisReturnType = this.returnType;
var targetReturnType = target.returnType;
var targetReturnType = value.returnType;
return thisReturnType == targetReturnType || thisReturnType.isAssignableTo(targetReturnType);
}

Expand Down
Loading

0 comments on commit a7b9244

Please sign in to comment.