Support 'this' in static functions, fixes #45; Fix propagation of 'ambient' flag

This commit is contained in:
dcodeIO 2018-03-20 12:02:05 +01:00
parent fea8e65a41
commit 2c0ddf4f80
12 changed files with 163 additions and 86 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2457,6 +2457,8 @@ function evaluateConstantOffset(compiler: Compiler, expression: Expression): i32
return value;
}
const allocateInternalName = "allocate_memory";
/** Compiles a memory allocation for an instance of the specified class. */
export function compileAllocate(
compiler: Compiler,
@ -2468,11 +2470,11 @@ export function compileAllocate(
var module = compiler.module;
var options = compiler.options;
var prototype = program.elementsLookup.get(options.allocateImpl);
var prototype = program.elementsLookup.get(allocateInternalName);
if (!prototype) {
program.error(
DiagnosticCode.Cannot_find_name_0,
reportNode.range, options.allocateImpl
reportNode.range, allocateInternalName
);
return module.createUnreachable();
}
@ -2498,6 +2500,8 @@ export function compileAllocate(
);
}
const abortInternalName = "abort";
/** Compiles an abort wired to the conditionally imported 'abort' function. */
export function compileAbort(
compiler: Compiler,
@ -2510,7 +2514,7 @@ export function compileAbort(
var stringType = program.typesLookup.get("string"); // might be intended
if (!stringType) return module.createUnreachable();
var abortPrototype = program.elementsLookup.get("abort"); // might be intended
var abortPrototype = program.elementsLookup.get(abortInternalName); // might be intended
if (!abortPrototype || abortPrototype.kind != ElementKind.FUNCTION_PROTOTYPE) return module.createUnreachable();
var abortInstance = (<FunctionPrototype>abortPrototype).resolve(); // reports

View File

@ -142,10 +142,6 @@ export class Options {
importMemory: bool = false;
/** Static memory start offset. */
memoryBase: u32 = 0;
/** Memory allocation implementation to use. */
allocateImpl: string = "allocate_memory";
/** Memory freeing implementation to use. */
freeImpl: string = "free_memory";
/** If true, generates information necessary for source maps. */
sourceMap: bool = false;
@ -513,7 +509,7 @@ export class Compiler extends DiagnosticEmitter {
var isConstant = global.isAny(CommonFlags.CONST) || global.is(CommonFlags.STATIC | CommonFlags.READONLY);
// handle imports
if (global.is(CommonFlags.DECLARE)) {
if (global.is(CommonFlags.AMBIENT)) {
// constant global
if (isConstant) {
@ -818,14 +814,14 @@ export class Compiler extends DiagnosticEmitter {
var declaration = instance.prototype.declaration;
var body = declaration.body;
if (body) {
if (instance.is(CommonFlags.DECLARE)) {
if (instance.is(CommonFlags.AMBIENT)) {
this.error(
DiagnosticCode.An_implementation_cannot_be_declared_in_ambient_contexts,
declaration.name.range
);
}
} else {
if (!instance.is(CommonFlags.DECLARE)) {
if (!instance.is(CommonFlags.AMBIENT)) {
this.error(
DiagnosticCode.Function_implementation_is_missing_or_not_immediately_following_the_declaration,
declaration.name.range
@ -4231,7 +4227,7 @@ export class Compiler extends DiagnosticEmitter {
var trampolineSignature = new Signature(trampolineParameterTypes, commonReturnType, commonThisType);
var trampolineName = originalName + "|trampoline";
trampolineSignature.requiredParameters = maxArguments + 1;
trampoline = new Function(original.prototype, trampolineName, trampolineSignature, original.instanceMethodOf);
trampoline = new Function(original.prototype, trampolineName, trampolineSignature, original.memberOf);
trampoline.flags = original.flags;
trampoline.set(CommonFlags.COMPILED);
original.trampoline = trampoline;
@ -4463,7 +4459,9 @@ export class Compiler extends DiagnosticEmitter {
case NodeKind.THIS: {
let currentFunction = this.currentFunction;
if (currentFunction.is(CommonFlags.INSTANCE)) {
let thisType = assert(currentFunction.instanceMethodOf).type;
let parent = assert(currentFunction.memberOf);
assert(parent.kind == ElementKind.CLASS);
let thisType = (<Class>parent).type;
this.currentType = thisType;
return module.createGetLocal(0, thisType.toNativeType());
}
@ -4477,7 +4475,9 @@ export class Compiler extends DiagnosticEmitter {
case NodeKind.SUPER: {
let currentFunction = this.currentFunction;
if (currentFunction.is(CommonFlags.INSTANCE)) {
let base = assert(currentFunction.instanceMethodOf).base;
let parent = assert(currentFunction.memberOf);
assert(parent.kind == ElementKind.CLASS);
let base = (<Class>parent).base;
if (base) {
let superType = base.type;
this.currentType = superType;
@ -4971,10 +4971,12 @@ export class Compiler extends DiagnosticEmitter {
)) {
return module.createUnreachable();
}
if (instance.instanceMethodOf) {
if (instance.is(CommonFlags.INSTANCE)) {
let parent = assert(instance.memberOf);
assert(parent.kind == ElementKind.CLASS);
targetExpr = this.compileExpression(
<Expression>resolved.targetExpression,
instance.instanceMethodOf.type
(<Class>parent).type
);
this.currentType = signature.returnType;
return this.compileCallDirect(instance, [], propertyAccess, targetExpr);

View File

@ -87,10 +87,7 @@ export class Parser extends DiagnosticEmitter {
backlog: string[] = new Array();
/** Log of source file names already processed. */
seenlog: Set<string> = new Set();
currentDeclareStart: i32 = 0;
currentDeclareEnd: i32 = 0;
/** Optional handler to intercept comments while tokenizing. */
onComment: CommentHandler | null = null;
/** Constructs a new parser. */
@ -145,7 +142,7 @@ export class Parser extends DiagnosticEmitter {
/** Parses a top-level statement. */
parseTopLevelStatement(
tn: Tokenizer,
isNamespaceMember: bool = false
namespace: Node | null = null
): Statement | null {
var flags = CommonFlags.NONE;
var startPos: i32 = -1;
@ -188,11 +185,18 @@ export class Parser extends DiagnosticEmitter {
var declareStart: i32 = 0;
var declareEnd: i32 = 0;
var contextIsAmbient = namespace != null && namespace.is(CommonFlags.AMBIENT);
if (tn.skip(Token.DECLARE)) {
if (startPos < 0) startPos = tn.tokenPos;
flags |= CommonFlags.DECLARE;
this.currentDeclareStart = declareStart = tn.tokenPos;
this.currentDeclareEnd = declareEnd = tn.pos;
if (contextIsAmbient) {
this.error(
DiagnosticCode.A_declare_modifier_cannot_be_used_in_an_already_ambient_context,
tn.range()
); // recoverable
}
flags |= CommonFlags.DECLARE | CommonFlags.AMBIENT;
} else if (contextIsAmbient) {
flags |= CommonFlags.AMBIENT;
}
// parse the statement
@ -293,9 +297,9 @@ export class Parser extends DiagnosticEmitter {
tn.range(declareStart, declareEnd), "declare"
); // recoverable
}
if (!isNamespaceMember) {
if (!namespace) {
statement = this.parseStatement(tn, true);
}
} // TODO: else?
}
break;
}
@ -761,7 +765,7 @@ export class Parser extends DiagnosticEmitter {
if (!initializer) return null;
} else {
if (flags & CommonFlags.CONST) {
if (!(flags & CommonFlags.DECLARE)) {
if (!(flags & CommonFlags.AMBIENT)) {
this.error(
DiagnosticCode._const_declarations_must_be_initialized,
identifier.range
@ -1173,17 +1177,6 @@ export class Parser extends DiagnosticEmitter {
tn.range(signatureStart, tn.pos)
);
if (flags & CommonFlags.DECLARE) {
if (flags & CommonFlags.AMBIENT) {
this.error(
DiagnosticCode.A_declare_modifier_cannot_be_used_in_an_already_ambient_context,
tn.range(this.currentDeclareStart, this.currentDeclareEnd)
); // recoverable
} else {
flags |= CommonFlags.AMBIENT;
}
}
var body: Statement | null = null;
if (tn.skip(Token.OPENBRACE)) {
if (flags & CommonFlags.AMBIENT) {
@ -1393,17 +1386,6 @@ export class Parser extends DiagnosticEmitter {
return null;
}
if (flags & CommonFlags.DECLARE) {
if (flags & CommonFlags.AMBIENT) {
this.error(
DiagnosticCode.A_declare_modifier_cannot_be_used_in_an_already_ambient_context,
tn.range(this.currentDeclareStart, this.currentDeclareEnd)
); // recoverable
} else {
flags |= CommonFlags.AMBIENT;
}
}
var members = new Array<DeclarationStatement>();
if (!tn.skip(Token.CLOSEBRACE)) {
do {
@ -1744,20 +1726,21 @@ export class Parser extends DiagnosticEmitter {
let identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
if (tn.skip(Token.OPENBRACE)) {
let members = new Array<Statement>();
while (!tn.skip(Token.CLOSEBRACE)) {
let member = this.parseTopLevelStatement(tn, true);
if (!member) return null;
members.push(member);
}
let ret = Node.createNamespaceDeclaration(
let ns = Node.createNamespaceDeclaration(
identifier,
members,
decorators,
flags,
tn.range(startPos, tn.pos)
);
while (!tn.skip(Token.CLOSEBRACE)) {
let member = this.parseTopLevelStatement(tn, ns);
if (!member) return null;
member.parent = ns;
members.push(member);
}
tn.skip(Token.SEMICOLON);
return ret;
return ns;
} else {
this.error(
DiagnosticCode._0_expected,

View File

@ -579,7 +579,7 @@ export class Program extends DiagnosticEmitter {
simpleName,
internalName,
declaration,
null
classPrototype
);
classPrototype.members.set(simpleName, prototype);
this.elementsLookup.set(internalName, prototype);
@ -1748,7 +1748,7 @@ export class Program extends DiagnosticEmitter {
} else {
break;
}
// or inherited instance members on the cbase class while target is a class instance
// or inherited instance members on the base class while target is a class instance
} else if (target.kind == ElementKind.CLASS) {
if ((<Class>target).base) {
target = <Class>(<Class>target).base;
@ -1836,11 +1836,11 @@ export class Program extends DiagnosticEmitter {
case NodeKind.BINARY: { // TODO: string concatenation, mostly
throw new Error("not implemented");
}
case NodeKind.THIS: { // -> Class
let classType = contextualFunction.instanceMethodOf;
if (classType) {
case NodeKind.THIS: { // -> Class / ClassPrototype
let parent = contextualFunction.memberOf;
if (parent) {
if (!resolvedElement) resolvedElement = new ResolvedElement();
return resolvedElement.set(classType);
return resolvedElement.set(parent);
}
this.error(
DiagnosticCode._this_cannot_be_referenced_in_current_location,
@ -1849,10 +1849,10 @@ export class Program extends DiagnosticEmitter {
return null;
}
case NodeKind.SUPER: { // -> Class
let classType = contextualFunction.instanceMethodOf;
if (classType && (classType = classType.base)) {
let parent = contextualFunction.memberOf;
if (parent && parent.kind == ElementKind.CLASS && (parent = (<Class>parent).base)) {
if (!resolvedElement) resolvedElement = new ResolvedElement();
return resolvedElement.set(classType);
return resolvedElement.set(parent);
}
this.error(
DiagnosticCode._super_can_only_be_referenced_in_a_derived_class,
@ -2294,6 +2294,7 @@ export class FunctionPrototype extends Element {
var declaration = this.declaration;
var isInstance = this.is(CommonFlags.INSTANCE);
var classPrototype = this.classPrototype;
// inherit contextual type arguments as provided. might be be overridden.
var inheritedTypeArguments = contextualTypeArguments;
@ -2310,7 +2311,8 @@ export class FunctionPrototype extends Element {
// override with class type arguments if a partially resolved instance method
var classTypeArguments = this.classTypeArguments;
if (classTypeArguments) { // set only if partially resolved
let classDeclaration = (<ClassPrototype>assert(this.classPrototype)).declaration;
assert(this.is(CommonFlags.INSTANCE));
let classDeclaration = assert(classPrototype).declaration;
let classTypeParameters = classDeclaration.typeParameters;
let numClassTypeParameters = classTypeParameters.length;
assert(numClassTypeParameters == classTypeArguments.length);
@ -2344,12 +2346,9 @@ export class FunctionPrototype extends Element {
var classInstance: Class | null = null;
var thisType: Type | null = null;
if (isInstance) {
let classPrototype = assert(this.classPrototype);
classInstance = classPrototype.resolve(classTypeArguments, contextualTypeArguments); // reports
classInstance = assert(classPrototype).resolve(classTypeArguments, contextualTypeArguments); // reports
if (!classInstance) return null;
thisType = classInstance.type;
} else {
assert(!this.classPrototype);
}
// resolve signature node
@ -2386,7 +2385,7 @@ export class FunctionPrototype extends Element {
var internalName = this.internalName;
if (instanceKey.length) internalName += "<" + instanceKey + ">";
instance = new Function(this, internalName, signature, classInstance);
instance = new Function(this, internalName, signature, classInstance ? classInstance : classPrototype);
instance.contextualTypeArguments = contextualTypeArguments;
this.instances.set(instanceKey, instance);
return instance;
@ -2394,6 +2393,7 @@ export class FunctionPrototype extends Element {
/** Resolves this prototype partially by applying the specified inherited class type arguments. */
resolvePartial(classTypeArguments: Type[] | null): FunctionPrototype | null {
assert(this.is(CommonFlags.INSTANCE));
assert(this.classPrototype);
if (classTypeArguments && classTypeArguments.length) {
let partialPrototype = new FunctionPrototype(
@ -2465,8 +2465,8 @@ export class Function extends Element {
prototype: FunctionPrototype;
/** Function signature. */
signature: Signature;
/** If an instance method, the concrete class it is a member of. */
instanceMethodOf: Class | null;
/** If a member of another namespace-like element, the concrete element it is a member of. */
memberOf: Element | null;
/** Map of locals by name. */
locals: Map<string,Local> = new Map();
/** List of additional non-parameter locals. */
@ -2494,16 +2494,16 @@ export class Function extends Element {
prototype: FunctionPrototype,
internalName: string,
signature: Signature,
instanceMethodOf: Class | null = null
memberOf: Element | null = null
) {
super(prototype.program, prototype.simpleName, internalName);
this.prototype = prototype;
this.signature = signature;
this.instanceMethodOf = instanceMethodOf;
this.memberOf = memberOf;
this.flags = prototype.flags;
if (!(prototype.is(CommonFlags.BUILTIN) || prototype.is(CommonFlags.DECLARE))) {
let localIndex = 0;
if (instanceMethodOf) {
if (memberOf && memberOf.kind == ElementKind.CLASS) {
assert(this.is(CommonFlags.INSTANCE));
this.locals.set(
"this",
@ -2514,11 +2514,12 @@ export class Function extends Element {
assert(signature.thisType)
)
);
if (instanceMethodOf.contextualTypeArguments) {
let contextualTypeArguments = (<Class>memberOf).contextualTypeArguments;
if (contextualTypeArguments) {
if (!this.contextualTypeArguments) {
this.contextualTypeArguments = new Map();
}
for (let [inheritedName, inheritedType] of instanceMethodOf.contextualTypeArguments) {
for (let [inheritedName, inheritedType] of contextualTypeArguments) {
this.contextualTypeArguments.set(inheritedName, inheritedType);
}
}

View File

@ -4,9 +4,9 @@ declare const externalConstant: i32;
externalFunction();
assert(externalConstant == 1);
namespace my {
export declare function externalFunction(): void;
export declare const externalConstant: i32;
declare namespace my {
function externalFunction(): void;
const externalConstant: i32;
}
my.externalFunction();

View File

@ -0,0 +1,31 @@
(module
(type $i (func (result i32)))
(type $iiiiv (func (param i32 i32 i32 i32)))
(type $v (func))
(import "env" "abort" (func $abort (param i32 i32 i32 i32)))
(global $static-this/Foo.bar (mut i32) (i32.const 42))
(memory $0 1)
(data (i32.const 4) "\0e\00\00\00s\00t\00a\00t\00i\00c\00-\00t\00h\00i\00s\00.\00t\00s")
(export "memory" (memory $0))
(start $start)
(func $static-this/Foo.getBar (; 1 ;) (type $i) (result i32)
(get_global $static-this/Foo.bar)
)
(func $start (; 2 ;) (type $v)
(if
(i32.ne
(call $static-this/Foo.getBar)
(i32.const 42)
)
(block
(call $abort
(i32.const 0)
(i32.const 4)
(i32.const 8)
(i32.const 0)
)
(unreachable)
)
)
)
)

View File

@ -0,0 +1,8 @@
class Foo {
static bar: i32 = 42;
static getBar(): i32 {
return this.bar;
}
}
assert(Foo.getBar() == 42);

View File

@ -0,0 +1,36 @@
(module
(type $i (func (result i32)))
(type $iiiiv (func (param i32 i32 i32 i32)))
(type $v (func))
(import "env" "abort" (func $abort (param i32 i32 i32 i32)))
(global $static-this/Foo.bar (mut i32) (i32.const 42))
(global $HEAP_BASE i32 (i32.const 36))
(memory $0 1)
(data (i32.const 4) "\0e\00\00\00s\00t\00a\00t\00i\00c\00-\00t\00h\00i\00s\00.\00t\00s\00")
(export "memory" (memory $0))
(start $start)
(func $static-this/Foo.getBar (; 1 ;) (type $i) (result i32)
(return
(get_global $static-this/Foo.bar)
)
)
(func $start (; 2 ;) (type $v)
(if
(i32.eqz
(i32.eq
(call $static-this/Foo.getBar)
(i32.const 42)
)
)
(block
(call $abort
(i32.const 0)
(i32.const 4)
(i32.const 8)
(i32.const 0)
)
(unreachable)
)
)
)
)

View File

@ -2,10 +2,15 @@ declare namespace A {
namespace B {
export namespace C {
var aVar: i32;
const aConst: i32 = 0;
function aFunc(): void {}
const aConst: i32;
const aConstInvalid: i32 = 0; // 1039: Initializers are not allowed in ambient contexts.
function aFunc(): void;
function aFuncInvalid(): void {} // 1183: An implementation cannot be declared in ambient contexts.
enum AnEnum {}
class AClass {}
}
namespace D {
var aVar: i32;
}
}
}

View File

@ -2,10 +2,17 @@ declare namespace A {
namespace B {
export namespace C {
var aVar: i32;
const aConst: i32 = 0;
function aFunc(): void {}
const aConst: i32;
const aConstInvalid: i32 = 0;
function aFunc(): void;
function aFuncInvalid(): void {}
enum AnEnum {}
class AClass {}
}
namespace D {
var aVar: i32;
}
}
}
// ERROR 1039: "Initializers are not allowed in ambient contexts." in namespace.ts:6:31
// ERROR 1183: "An implementation cannot be declared in ambient contexts." in namespace.ts:8:36