Skip to content

BREAKING CHANGE: Make class fields a special kind of property #2548

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
},
"engineStrict": true,
"dependencies": {
"binaryen": "110.0.0-nightly.20221019",
"binaryen": "110.0.0-nightly.20221105",
"long": "^5.2.0"
},
"devDependencies": {
Expand Down
9 changes: 9 additions & 0 deletions src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1622,6 +1622,15 @@ export const enum SourceKind {

/** A top-level source node. */
export class Source extends Node {

/** Gets the special native source. */
static get native(): Source {
let source = Source._native;
if (!source) Source._native = source = new Source(SourceKind.LibraryEntry, LIBRARY_PREFIX + "native.ts", "[native code]");
return source;
}
private static _native: Source | null = null;

constructor(
/** Source kind. */
public sourceKind: SourceKind,
Expand Down
29 changes: 15 additions & 14 deletions src/bindings/js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
LiteralExpression,
StringLiteralExpression,
TemplateLiteralExpression,
findDecorator
findDecorator,
Source
} from "../ast";

import {
Expand All @@ -22,7 +23,7 @@ import {
Interface,
Enum,
EnumValue,
Field
PropertyPrototype
} from "../program";

import {
Expand Down Expand Up @@ -446,10 +447,6 @@ export class JSBuilder extends ExportsWalker {
this.visitClass(name, element);
}

visitField(name: string, element: Field): void {
// not implemented
}

visitNamespace(name: string, element: Element): void {
// not implemented
}
Expand Down Expand Up @@ -1241,12 +1238,14 @@ export class JSBuilder extends ExportsWalker {
for (let _keys = Map_keys(members), i = 0, k = _keys.length; i < k; ++i) {
let memberName = _keys[i];
let member = assert(members.get(memberName));
if (member.kind != ElementKind.Field) continue;
let field = <Field>member;
if (member.kind != ElementKind.PropertyPrototype) continue;
let property = (<PropertyPrototype>member).instance; // resolved during class finalization
if (!property || !property.isField) continue;
assert(property.memoryOffset >= 0);
indent(sb, this.indentLevel);
sb.push(field.name);
sb.push(property.name);
sb.push(": ");
this.makeLiftFromMemory(field.type, sb, "pointer + " + field.memoryOffset.toString());
this.makeLiftFromMemory(property.type, sb, "pointer + " + property.memoryOffset.toString());
sb.push(",\n");
}
}
Expand Down Expand Up @@ -1283,10 +1282,12 @@ export class JSBuilder extends ExportsWalker {
for (let _keys = Map_keys(members), i = 0, k = _keys.length; i < k; ++i) {
let memberName = _keys[i];
let member = assert(members.get(memberName));
if (member.kind != ElementKind.Field) continue;
let field = <Field>member;
if (member.kind != ElementKind.PropertyPrototype) continue;
let property = (<PropertyPrototype>member).instance; // resolved during class finalization
if (!property || !property.isField) continue;
assert(property.memoryOffset >= 0);
indent(sb, this.indentLevel);
this.makeLowerToMemory(field.type, sb, "pointer + " + field.memoryOffset.toString(), "value." + memberName);
this.makeLowerToMemory(property.type, sb, "pointer + " + property.memoryOffset.toString(), "value." + memberName);
sb.push(";\n");
}
}
Expand Down Expand Up @@ -1343,7 +1344,7 @@ function isPlainObject(clazz: Class): bool {
if (member.isAny(CommonFlags.Private | CommonFlags.Protected)) return false;
if (member.is(CommonFlags.Constructor)) {
// a generated constructor is ok
if (member.declaration.range != member.program.nativeRange) return false;
if (member.declaration.range != Source.native.range) return false;
}
}
}
Expand Down
27 changes: 14 additions & 13 deletions src/bindings/tsd.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import {
Source
} from "../ast";

import {
CommonFlags
} from "../common";
Expand All @@ -10,8 +14,8 @@ import {
Interface,
Enum,
ElementKind,
Field,
Element
Element,
PropertyPrototype
} from "../program";

import {
Expand Down Expand Up @@ -168,10 +172,6 @@ export class TSDBuilder extends ExportsWalker {
// not implemented
}

visitField(name: string, element: Field): void {
// not implemented
}

visitNamespace(name: string, element: Element): void {
// not implemented
}
Expand Down Expand Up @@ -238,7 +238,7 @@ export class TSDBuilder extends ExportsWalker {
if (member.isAny(CommonFlags.Private | CommonFlags.Protected)) return false;
if (member.is(CommonFlags.Constructor)) {
// a generated constructor is ok
if (member.declaration.range != this.program.nativeRange) return false;
if (member.declaration.range != Source.native.range) return false;
}
}
}
Expand Down Expand Up @@ -347,15 +347,16 @@ export class TSDBuilder extends ExportsWalker {
for (let _keys = Map_keys(members), i = 0, k = _keys.length; i < k; ++i) {
let memberName = _keys[i];
let member = assert(members.get(memberName));
if (member.kind != ElementKind.Field) continue;
let field = <Field>member;
if (member.kind != ElementKind.PropertyPrototype) continue;
let property = (<PropertyPrototype>member).instance; // resolved during class finalization
if (!property || !property.isField) continue;
sb.push(" /** @type `");
sb.push(field.type.toString());
sb.push(property.type.toString());
sb.push("` */\n ");
sb.push(field.name);
sb.push(property.name);
sb.push(": ");
sb.push(this.toTypeScriptType(field.type, mode));
if (this.fieldAcceptsUndefined(field.type)) {
sb.push(this.toTypeScriptType(property.type, mode));
if (this.fieldAcceptsUndefined(property.type)) {
sb.push(" | TOmittable");
}
sb.push(";\n");
Expand Down
7 changes: 0 additions & 7 deletions src/bindings/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
Enum,
Class,
Interface,
Field,
File,
FunctionPrototype,
Global,
Expand Down Expand Up @@ -95,11 +94,6 @@ export abstract class ExportsWalker {
this.visitClassInstances(name, <ClassPrototype>element);
break;
}
case ElementKind.Field: {
let fieldInstance = <Field>element;
if (fieldInstance.is(CommonFlags.Compiled)) this.visitField(name, fieldInstance);
break;
}
case ElementKind.PropertyPrototype: {
let propertyInstance = (<PropertyPrototype>element).instance;
if (!propertyInstance) break;
Expand Down Expand Up @@ -150,7 +144,6 @@ export abstract class ExportsWalker {
abstract visitFunction(name: string, element: Function): void;
abstract visitClass(name: string, element: Class): void;
abstract visitInterface(name: string, element: Interface): void;
abstract visitField(name: string, element: Field): void;
abstract visitNamespace(name: string, element: Element): void;
abstract visitAlias(name: string, element: Element, originalName: string): void;
}
Expand Down
127 changes: 33 additions & 94 deletions src/builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,10 @@ import {
import {
ElementKind,
FunctionPrototype,
Field,
Global,
DecoratorFlags,
ClassPrototype,
Class
Class,
PropertyPrototype
} from "./program";

import {
Expand Down Expand Up @@ -1075,11 +1074,12 @@ function builtin_offsetof(ctx: BuiltinContext): ExpressionRef {
return module.unreachable();
}
let fieldName = (<StringLiteralExpression>firstOperand).value;
let classMembers = classReference.members;
if (classMembers && classMembers.has(fieldName)) {
let member = assert(classMembers.get(fieldName));
if (member.kind == ElementKind.Field) {
return contextualUsize(compiler, i64_new((<Field>member).memoryOffset), contextualType);
let fieldMember = classReference.getMember(fieldName);
if (fieldMember && fieldMember.kind == ElementKind.PropertyPrototype) {
let property = (<PropertyPrototype>fieldMember).instance;
if (property && property.isField) {
assert(property.memoryOffset >= 0);
return contextualUsize(compiler, i64_new(property.memoryOffset), contextualType);
}
}
compiler.error(
Expand Down Expand Up @@ -10261,32 +10261,31 @@ function ensureVisitMembersOf(compiler: Compiler, instance: Class): void {
// TODO: for (let member of members.values()) {
for (let _values = Map_values(members), j = 0, l = _values.length; j < l; ++j) {
let member = unchecked(_values[j]);
if (member.kind == ElementKind.Field) {
if ((<Field>member).parent == instance) {
let fieldType = (<Field>member).type;
if (fieldType.isManaged) {
let fieldOffset = (<Field>member).memoryOffset;
assert(fieldOffset >= 0);
needsTempValue = true;
body.push(
// if ($2 = value) __visit($2, $1)
module.if(
module.local_tee(2,
module.load(sizeTypeSize, false,
module.local_get(0, sizeTypeRef),
sizeTypeRef, fieldOffset
),
false // internal
),
module.call(visitInstance.internalName, [
module.local_get(2, sizeTypeRef), // value
module.local_get(1, TypeRef.I32) // cookie
], TypeRef.None)
)
);
}
}
}
if (member.kind != ElementKind.PropertyPrototype) continue;
// Class should have resolved fields during finalization
let property = (<PropertyPrototype>member).instance;
if (!property) continue;
let fieldType = property.type;
if (!property.isField || property.getBoundClassOrInterface() != instance || !fieldType.isManaged) continue;
let fieldOffset = property.memoryOffset;
assert(fieldOffset >= 0);
needsTempValue = true;
body.push(
// if ($2 = value) __visit($2, $1)
module.if(
module.local_tee(2,
module.load(sizeTypeSize, false,
module.local_get(0, sizeTypeRef),
sizeTypeRef, fieldOffset
),
false // internal
),
module.call(visitInstance.internalName, [
module.local_get(2, sizeTypeRef), // value
module.local_get(1, TypeRef.I32) // cookie
], TypeRef.None)
)
);
}
}
}
Expand Down Expand Up @@ -10458,66 +10457,6 @@ export function compileRTTI(compiler: Compiler): void {
}
}

/** Compiles a class-specific instanceof helper, checking a ref against all concrete instances. */
export function compileClassInstanceOf(compiler: Compiler, prototype: ClassPrototype): void {
let module = compiler.module;
let sizeTypeRef = compiler.options.sizeTypeRef;
let instanceofInstance = assert(prototype.program.instanceofInstance);
compiler.compileFunction(instanceofInstance);

let stmts = new Array<ExpressionRef>();

// if (!ref) return false
stmts.push(
module.if(
module.unary(
sizeTypeRef == TypeRef.I64
? UnaryOp.EqzI64
: UnaryOp.EqzI32,
module.local_get(0, sizeTypeRef)
),
module.return(
module.i32(0)
)
)
);

// if (__instanceof(ref, ID[i])) return true
let instances = prototype.instances;
if (instances && instances.size > 0) {
// TODO: for (let instance of instances.values()) {
for (let _values = Map_values(instances), i = 0, k = _values.length; i < k; ++i) {
let instance = unchecked(_values[i]);
stmts.push(
module.if(
module.call(instanceofInstance.internalName, [
module.local_get(0, sizeTypeRef),
module.i32(instance.id)
], TypeRef.I32),
module.return(
module.i32(1)
)
)
);
}
}

// return false
stmts.push(
module.return(
module.i32(0)
)
);

module.addFunction(
`${prototype.internalName}~instanceof`,
sizeTypeRef,
TypeRef.I32,
null,
module.flatten(stmts)
);
}

// Helpers

let checkConstantType_expr: ExpressionRef = 0;
Expand Down
5 changes: 2 additions & 3 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ export const enum CommonFlags {
Scoped = 1 << 26,
/** Is a stub. */
Stub = 1 << 27,
/** Is a virtual method. */
Virtual = 1 << 28,
/** Is an overridden method. */
Overridden = 1 << 28,
/** Is (part of) a closure. */
Closure = 1 << 29,

Expand Down Expand Up @@ -257,7 +257,6 @@ export namespace CommonNames {
export const link = "__link";
export const collect = "__collect";
export const typeinfo = "__typeinfo";
export const instanceof_ = "__instanceof";
export const visit = "__visit";
export const newBuffer = "__newBuffer";
export const newArray = "__newArray";
Expand Down
Loading