diff --git a/.gitignore b/.gitignore index cb7d0b9ef8..68c09d2201 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ out/ raw/ .history *.backup +.idea/ diff --git a/NOTICE b/NOTICE index 3cbcecc156..fda564229f 100644 --- a/NOTICE +++ b/NOTICE @@ -20,6 +20,7 @@ under the licensing terms detailed in LICENSE: * Jeffrey Charles * Vladimir Tikhonov * Duncan Uszkay +* Saúl Cabrera Portions of this software are derived from third-party works licensed under the following terms: diff --git a/src/ast.ts b/src/ast.ts index 155b1603fc..bb82e21bf9 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -114,9 +114,9 @@ export enum NodeKind { /** Base class of all nodes. */ export abstract class Node { /** Node kind indicator. */ - kind: NodeKind; + kind!: NodeKind; /** Source range. */ - range: Range; + range!: Range; // types @@ -1173,7 +1173,7 @@ export abstract class TypeNode extends Node { // kind varies /** Whether nullable or not. */ - isNullable: bool; + isNullable!: bool; /** Tests if this type has a generic component matching one of the given type parameters. */ hasGenericComponent(typeParameterNodes: TypeParameterNode[]): bool { @@ -1211,17 +1211,17 @@ export abstract class TypeNode extends Node { /** Represents a type name. */ export class TypeName extends Node { /** Identifier of this part. */ - identifier: IdentifierExpression; + identifier!: IdentifierExpression; /** Next part of the type name or `null` if this is the last part. */ - next: TypeName | null; + next: TypeName | null = null; } /** Represents a named type. */ export class NamedTypeNode extends TypeNode { /** Type name. */ - name: TypeName; + name!: TypeName; /** Type argument references. */ - typeArguments: TypeNode[] | null; + typeArguments: TypeNode[] | null = null; get hasTypeArguments(): bool { var typeArguments = this.typeArguments; @@ -1232,21 +1232,21 @@ export class NamedTypeNode extends TypeNode { /** Represents a function type. */ export class FunctionTypeNode extends TypeNode { /** Accepted parameters. */ - parameters: ParameterNode[]; + parameters!: ParameterNode[]; /** Return type. */ - returnType: TypeNode; + returnType!: TypeNode; /** Explicitly provided this type, if any. */ - explicitThisType: NamedTypeNode | null; // can't be a function + explicitThisType: NamedTypeNode | null = null; // can't be a function } /** Represents a type parameter. */ export class TypeParameterNode extends Node { /** Identifier reference. */ - name: IdentifierExpression; + name!: IdentifierExpression; /** Extended type reference, if any. */ - extendsType: NamedTypeNode | null; // can't be a function + extendsType: NamedTypeNode | null = null; // can't be a function /** Default type if omitted, if any. */ - defaultType: NamedTypeNode | null; // can't be a function + defaultType: NamedTypeNode | null = null; // can't be a function } /** Represents the kind of a parameter. */ @@ -1262,13 +1262,13 @@ export enum ParameterKind { /** Represents a function parameter. */ export class ParameterNode extends Node { /** Parameter kind. */ - parameterKind: ParameterKind; + parameterKind!: ParameterKind; /** Parameter name. */ - name: IdentifierExpression; + name!: IdentifierExpression; /** Parameter type. */ - type: TypeNode; + type!: TypeNode; /** Initializer expression, if present. */ - initializer: Expression | null; + initializer: Expression | null = null; /** Implicit field declaration, if applicable. */ implicitFieldDeclaration: FieldDeclaration | null = null; /** Common flags indicating specific traits. */ @@ -1375,11 +1375,11 @@ export namespace DecoratorKind { /** Represents a decorator. */ export class DecoratorNode extends Node { /** Built-in kind, if applicable. */ - decoratorKind: DecoratorKind; + decoratorKind!: DecoratorKind; /** Name expression. */ - name: Expression; + name!: Expression; /** Argument expressions. */ - arguments: Expression[] | null; + arguments: Expression[] | null = null; } /** Comment kinds. */ @@ -1395,9 +1395,9 @@ export enum CommentKind { /** Represents a comment. */ export class CommentNode extends Node { /** Comment kind. */ - commentKind: CommentKind; + commentKind!: CommentKind; /** Comment text. */ - text: string; + text!: string; } // expressions @@ -1408,9 +1408,9 @@ export abstract class Expression extends Node { } /** Represents an identifier expression. */ export class IdentifierExpression extends Expression { /** Textual name. */ - text: string; + text!: string; /** Whether quoted or not. */ - isQuoted: bool; + isQuoted!: bool; } /** Indicates the kind of a literal. */ @@ -1426,13 +1426,13 @@ export enum LiteralKind { /** Base class of all literal expressions. */ export abstract class LiteralExpression extends Expression { /** Specific literal kind. */ - literalKind: LiteralKind; + literalKind!: LiteralKind; } /** Represents an `[]` literal expression. */ export class ArrayLiteralExpression extends LiteralExpression { /** Nested element expressions. */ - elementExpressions: (Expression | null)[]; + elementExpressions!: (Expression | null)[]; } /** Indicates the kind of an assertion. */ @@ -1446,31 +1446,31 @@ export enum AssertionKind { /** Represents an assertion expression. */ export class AssertionExpression extends Expression { /** Specific kind of this assertion. */ - assertionKind: AssertionKind; + assertionKind!: AssertionKind; /** Expression being asserted. */ - expression: Expression; + expression!: Expression; /** Target type. */ - toType: TypeNode | null; + toType: TypeNode | null = null; } /** Represents a binary expression. */ export class BinaryExpression extends Expression { /** Operator token. */ - operator: Token; + operator!: Token; /** Left-hand side expression */ - left: Expression; + left!: Expression; /** Right-hand side expression. */ - right: Expression; + right!: Expression; } /** Represents a call expression. */ export class CallExpression extends Expression { /** Called expression. Usually an identifier or property access expression. */ - expression: Expression; + expression!: Expression; /** Provided type arguments. */ - typeArguments: TypeNode[] | null; + typeArguments: TypeNode[] | null = null; /** Provided arguments. */ - arguments: Expression[]; + arguments!: Expression[]; /** Gets the type arguments range for reporting. */ get typeArgumentsRange(): Range { @@ -1498,13 +1498,13 @@ export class CallExpression extends Expression { /** Represents a class expression using the 'class' keyword. */ export class ClassExpression extends Expression { /** Inline class declaration. */ - declaration: ClassDeclaration; + declaration!: ClassDeclaration; } /** Represents a comma expression composed of multiple expressions. */ export class CommaExpression extends Expression { /** Sequential expressions. */ - expressions: Expression[]; + expressions!: Expression[]; } /** Represents a `constructor` expression. */ @@ -1514,45 +1514,45 @@ export class ConstructorExpression extends IdentifierExpression { /** Represents an element access expression, e.g., array access. */ export class ElementAccessExpression extends Expression { /** Expression being accessed. */ - expression: Expression; + expression!: Expression; /** Element of the expression being accessed. */ - elementExpression: Expression; + elementExpression!: Expression; } /** Represents a float literal expression. */ export class FloatLiteralExpression extends LiteralExpression { /** Float value. */ - value: f64; + value!: f64; } /** Represents a function expression using the 'function' keyword. */ export class FunctionExpression extends Expression { /** Inline function declaration. */ - declaration: FunctionDeclaration; + declaration!: FunctionDeclaration; } /** Represents an `instanceof` expression. */ export class InstanceOfExpression extends Expression { /** Expression being asserted. */ - expression: Expression; + expression!: Expression; /** Type to test for. */ - isType: TypeNode; + isType!: TypeNode; } /** Represents an integer literal expression. */ export class IntegerLiteralExpression extends LiteralExpression { /** Integer value. */ - value: i64; + value!: i64; } /** Represents a `new` expression. Like a call but with its own kind. */ export class NewExpression extends Expression { /** Type being constructed. */ - typeName: TypeName; + typeName!: TypeName; /** Provided type arguments. */ - typeArguments: TypeNode[] | null; + typeArguments: TypeNode[] | null = null; /** Provided arguments. */ - arguments: Expression[]; + arguments!: Expression[]; /** Gets the type arguments range for reporting. */ get typeArgumentsRange(): Range { @@ -1582,47 +1582,47 @@ export class NullExpression extends IdentifierExpression { /** Represents an object literal expression. */ export class ObjectLiteralExpression extends LiteralExpression { /** Field names. */ - names: IdentifierExpression[]; + names!: IdentifierExpression[]; /** Field values. */ - values: Expression[]; + values!: Expression[]; } /** Represents a parenthesized expression. */ export class ParenthesizedExpression extends Expression { /** Expression in parenthesis. */ - expression: Expression; + expression!: Expression; } /** Represents a property access expression. */ export class PropertyAccessExpression extends Expression { /** Expression being accessed. */ - expression: Expression; + expression!: Expression; /** Property of the expression being accessed. */ - property: IdentifierExpression; + property!: IdentifierExpression; } /** Represents a regular expression literal expression. */ export class RegexpLiteralExpression extends LiteralExpression { /** Regular expression pattern. */ - pattern: string; + pattern!: string; /** Regular expression flags. */ - patternFlags: string; + patternFlags!: string; } /** Represents a ternary expression, i.e., short if notation. */ export class TernaryExpression extends Expression { /** Condition expression. */ - condition: Expression; + condition!: Expression; /** Expression executed when condition is `true`. */ - ifThen: Expression; + ifThen!: Expression; /** Expression executed when condition is `false`. */ - ifElse: Expression; + ifElse!: Expression; } /** Represents a string literal expression. */ export class StringLiteralExpression extends LiteralExpression { /** String value without quotes. */ - value: string; + value!: string; } /** Represents a `super` expression. */ @@ -1644,9 +1644,9 @@ export class FalseExpression extends IdentifierExpression { /** Base class of all unary expressions. */ export abstract class UnaryExpression extends Expression { /** Operator token. */ - operator: Token; + operator!: Token; /** Operand expression. */ - operand: Expression; + operand!: Expression; } /** Represents a unary postfix expression, e.g. a postfix increment. */ @@ -1763,7 +1763,7 @@ export class Source extends Node { /** Base class of all declaration statements. */ export abstract class DeclarationStatement extends Statement { /** Simple name being declared. */ - name: IdentifierExpression; + name!: IdentifierExpression; /** Array of decorators. */ decorators: DecoratorNode[] | null = null; /** Common flags indicating specific traits. */ @@ -1780,41 +1780,41 @@ export abstract class DeclarationStatement extends Statement { /** Represents an index signature declaration. */ export class IndexSignatureDeclaration extends DeclarationStatement { /** Key type. */ - keyType: NamedTypeNode; + keyType!: NamedTypeNode; /** Value type. */ - valueType: TypeNode; + valueType!: TypeNode; } /** Base class of all variable-like declaration statements. */ export abstract class VariableLikeDeclarationStatement extends DeclarationStatement { /** Variable type. */ - type: TypeNode | null; + type: TypeNode | null = null; /** Variable initializer. */ - initializer: Expression | null; + initializer: Expression | null = null; } /** Represents a block statement. */ export class BlockStatement extends Statement { /** Contained statements. */ - statements: Statement[]; + statements!: Statement[]; } /** Represents a `break` statement. */ export class BreakStatement extends Statement { /** Target label, if applicable. */ - label: IdentifierExpression | null; + label: IdentifierExpression | null = null; } /** Represents a `class` declaration. */ export class ClassDeclaration extends DeclarationStatement { /** Accepted type parameters. */ - typeParameters: TypeParameterNode[] | null; + typeParameters: TypeParameterNode[] | null = null; /** Base class type being extended, if any. */ - extendsType: NamedTypeNode | null; // can't be a function + extendsType: NamedTypeNode | null = null; // can't be a function /** Interface types being implemented, if any. */ - implementsTypes: NamedTypeNode[] | null; // can't be functions + implementsTypes: NamedTypeNode[] | null = null; // can't be functions /** Class member declarations. */ - members: DeclarationStatement[]; + members!: DeclarationStatement[]; get isGeneric(): bool { var typeParameters = this.typeParameters; @@ -1825,15 +1825,15 @@ export class ClassDeclaration extends DeclarationStatement { /** Represents a `continue` statement. */ export class ContinueStatement extends Statement { /** Target label, if applicable. */ - label: IdentifierExpression | null; + label: IdentifierExpression | null = null; } /** Represents a `do` statement. */ export class DoStatement extends Statement { /** Statement being looped over. */ - statement: Statement; + statement!: Statement; /** Condition when to repeat. */ - condition: Expression; + condition!: Expression; } /** Represents an empty statement, i.e., a semicolon terminating nothing. */ @@ -1843,53 +1843,53 @@ export class EmptyStatement extends Statement { /** Represents an `enum` declaration. */ export class EnumDeclaration extends DeclarationStatement { /** Enum value declarations. */ - values: EnumValueDeclaration[]; + values!: EnumValueDeclaration[]; } /** Represents a value of an `enum` declaration. */ export class EnumValueDeclaration extends VariableLikeDeclarationStatement { /** Value expression. */ - value: Expression | null; + value: Expression | null = null; } /** Represents an `export import` statement of an interface. */ export class ExportImportStatement extends Statement { /** Identifier being imported. */ - name: IdentifierExpression; + name!: IdentifierExpression; /** Identifier being exported. */ - externalName: IdentifierExpression; + externalName!: IdentifierExpression; } /** Represents a member of an `export` statement. */ export class ExportMember extends Node { /** Local identifier. */ - localName: IdentifierExpression; + localName!: IdentifierExpression; /** Exported identifier. */ - exportedName: IdentifierExpression; + exportedName!: IdentifierExpression; } /** Represents an `export` statement. */ export class ExportStatement extends Statement { /** Array of members if a set of named exports, or `null` if a file export. */ - members: ExportMember[] | null; + members: ExportMember[] | null = null; /** Path being exported from, if applicable. */ - path: StringLiteralExpression | null; + path: StringLiteralExpression | null = null; /** Internal path being referenced, if `path` is set. */ - internalPath: string | null; + internalPath: string | null = null; /** Whether this is a declared export. */ - isDeclare: bool; + isDeclare!: bool; } /** Represents an `export default` statement. */ export class ExportDefaultStatement extends Statement { /** Declaration being exported as default. */ - declaration: DeclarationStatement; + declaration!: DeclarationStatement; } /** Represents an expression that is used as a statement. */ export class ExpressionStatement extends Statement { /** Expression being used as a statement.*/ - expression: Expression; + expression!: Expression; } /** Represents a field declaration within a `class`. */ @@ -1901,23 +1901,23 @@ export class FieldDeclaration extends VariableLikeDeclarationStatement { /** Represents a `for` statement. */ export class ForStatement extends Statement { /** Initializer statement, if present. Either a `VariableStatement` or `ExpressionStatement`. */ - initializer: Statement | null; + initializer: Statement | null = null; /** Condition expression, if present. */ - condition: Expression | null; + condition: Expression | null = null; /** Incrementor expression, if present. */ - incrementor: Expression | null; + incrementor: Expression | null = null; /** Statement being looped over. */ - statement: Statement; + statement!: Statement; } /** Represents a `for..of` statement. */ export class ForOfStatement extends Statement { /** Variable statement. Either a `VariableStatement` or `ExpressionStatement` of `IdentifierExpression`. */ - variable: Statement; + variable!: Statement; /** Iterable expression being iterated. */ - iterable: Expression; + iterable!: Expression; /** Statement being looped over. */ - statement: Statement; + statement!: Statement; } /** Indicates the kind of an array function. */ @@ -1933,13 +1933,13 @@ export const enum ArrowKind { /** Represents a `function` declaration. */ export class FunctionDeclaration extends DeclarationStatement { /** Type parameters, if any. */ - typeParameters: TypeParameterNode[] | null; + typeParameters: TypeParameterNode[] | null = null; /** Function signature. */ - signature: FunctionTypeNode; + signature!: FunctionTypeNode; /** Body statement. Usually a block. */ - body: Statement | null; + body: Statement | null = null; /** Arrow function kind, if applicable. */ - arrowKind: ArrowKind; + arrowKind!: ArrowKind; get isGeneric(): bool { var typeParameters = this.typeParameters; @@ -1964,29 +1964,29 @@ export class FunctionDeclaration extends DeclarationStatement { /** Represents an `if` statement. */ export class IfStatement extends Statement { /** Condition. */ - condition: Expression; + condition!: Expression; /** Statement executed when condition is `true`. */ - ifTrue: Statement; + ifTrue!: Statement; /** Statement executed when condition is `false`. */ - ifFalse: Statement | null; + ifFalse: Statement | null = null; } /** Represents an `import` declaration part of an {@link ImportStatement}. */ export class ImportDeclaration extends DeclarationStatement { /** Identifier being imported. */ - foreignName: IdentifierExpression; + foreignName!: IdentifierExpression; } /** Represents an `import` statement. */ export class ImportStatement extends Statement { /** Array of member declarations or `null` if an asterisk import. */ - declarations: ImportDeclaration[] | null; + declarations: ImportDeclaration[] | null = null; /** Name of the local namespace, if an asterisk import. */ - namespaceName: IdentifierExpression | null; + namespaceName: IdentifierExpression | null = null; /** Path being imported from. */ - path: StringLiteralExpression; + path!: StringLiteralExpression; /** Internal path being referenced. */ - internalPath: string; + internalPath!: string; } /** Represents an `interfarce` declaration. */ @@ -2000,55 +2000,55 @@ export class MethodDeclaration extends FunctionDeclaration { /** Represents a `namespace` declaration. */ export class NamespaceDeclaration extends DeclarationStatement { /** Array of namespace members. */ - members: Statement[]; + members!: Statement[]; } /** Represents a `return` statement. */ export class ReturnStatement extends Statement { /** Value expression being returned, if present. */ - value: Expression | null; + value: Expression | null = null; } /** Represents a single `case` within a `switch` statement. */ export class SwitchCase extends Node { /** Label expression. `null` indicates the default case. */ - label: Expression | null; + label: Expression | null = null; /** Contained statements. */ - statements: Statement[]; + statements!: Statement[]; } /** Represents a `switch` statement. */ export class SwitchStatement extends Statement { /** Condition expression. */ - condition: Expression; + condition!: Expression; /** Contained cases. */ - cases: SwitchCase[]; + cases!: SwitchCase[]; } /** Represents a `throw` statement. */ export class ThrowStatement extends Statement { /** Value expression being thrown. */ - value: Expression; + value!: Expression; } /** Represents a `try` statement. */ export class TryStatement extends Statement { /** Contained statements. */ - statements: Statement[]; + statements!: Statement[]; /** Exception variable name, if a `catch` clause is present. */ - catchVariable: IdentifierExpression | null; + catchVariable: IdentifierExpression | null = null; /** Statements being executed on catch, if a `catch` clause is present. */ - catchStatements: Statement[] | null; + catchStatements: Statement[] | null = null; /** Statements being executed afterwards, if a `finally` clause is present. */ - finallyStatements: Statement[] | null; + finallyStatements: Statement[] | null = null; } /** Represents a `type` declaration. */ export class TypeDeclaration extends DeclarationStatement { /** Type parameters, if any. */ - typeParameters: TypeParameterNode[] | null; + typeParameters: TypeParameterNode[] | null = null; /** Type being aliased. */ - type: TypeNode; + type!: TypeNode; } /** Represents a variable declaration part of a {@link VariableStatement}. */ @@ -2058,23 +2058,23 @@ export class VariableDeclaration extends VariableLikeDeclarationStatement { /** Represents a variable statement wrapping {@link VariableDeclaration}s. */ export class VariableStatement extends Statement { /** Array of decorators. */ - decorators: DecoratorNode[] | null; + decorators: DecoratorNode[] | null = null; /** Array of member declarations. */ - declarations: VariableDeclaration[]; + declarations!: VariableDeclaration[]; } /** Represents a void statement dropping an expression's value. */ export class VoidStatement extends Statement { /** Expression being dropped. */ - expression: Expression; + expression!: Expression; } /** Represents a `while` statement. */ export class WhileStatement extends Statement { /** Condition expression. */ - condition: Expression; + condition!: Expression; /** Statement being looped over. */ - statement: Statement; + statement!: Statement; } /** Finds the first decorator matching the specified kind. */ diff --git a/src/builtins.ts b/src/builtins.ts index 671ba4221c..5270b5531c 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -597,19 +597,19 @@ export namespace BuiltinNames { /** Builtin compilation context. */ export class BuiltinContext { /** Compiler reference. */ - compiler: Compiler; + compiler!: Compiler; /** Prototype being called. */ - prototype: FunctionPrototype; + prototype!: FunctionPrototype; /** Provided type arguments. */ - typeArguments: Type[] | null; + typeArguments: Type[] | null = null; /** Provided operands. */ - operands: Expression[]; + operands!: Expression[]; /** Contextual type. */ - contextualType: Type; + contextualType!: Type; /** Respective call expression. */ - reportNode: CallExpression; + reportNode!: CallExpression; /** Whether originating from inline assembly. */ - contextIsExact: bool; + contextIsExact!: bool; } /** Global builtins map. */ diff --git a/src/compiler.ts b/src/compiler.ts index 0f0d528ebb..0a7b79205c 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -91,6 +91,7 @@ import { import { FlowFlags, + FieldFlags, Flow, LocalFlags, ConditionKind, @@ -319,13 +320,13 @@ export class Compiler extends DiagnosticEmitter { module: Module; /** Current control flow. */ - currentFlow: Flow; + currentFlow!: Flow; /** Current parent element if not a function, i.e. an enum or namespace. */ currentParent: Element | null = null; /** Current type in compilation. */ currentType: Type = Type.void; /** Start function statements. */ - currentBody: ExpressionRef[]; + currentBody!: ExpressionRef[]; /** Counting memory offset. */ memoryOffset: i64; /** Memory segments being compiled. */ @@ -1534,6 +1535,7 @@ export class Compiler extends DiagnosticEmitter { } } this.ensureConstructor(instance, instance.identifierNode); + this.checkFieldInitialization(instance); var instanceMembers = instance.members; if (instanceMembers) { // TODO: for (let element of instanceMembers.values()) { @@ -1561,6 +1563,26 @@ export class Compiler extends DiagnosticEmitter { return true; } + checkFieldInitialization(instance: Class): void { + const flow = instance.constructorInstance!.flow; + const instanceMembers = instance.members; + if (instanceMembers) { + for (let _values = Map_values(instanceMembers), i = 0, k = _values.length; i < k; ++i) { + const element = unchecked(_values[i]); + if (element.kind == ElementKind.FIELD && element.parent == instance) { + const field = element; + if (!flow.isFieldFlag(field.internalName, FieldFlags.INITIALIZED)) { + this.error( + DiagnosticCode.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_constructor, + field.declaration.name.range, + field.declaration.name.text + ); + } + } + } + } + } + /** Compiles an instance field to a getter and a setter. */ compileField(instance: Field): bool { this.compileFieldGetter(instance); @@ -2755,6 +2777,9 @@ export class Compiler extends DiagnosticEmitter { var currentBlock = module.block("case0|" + context, breaks, NativeType.None); var commonCategorical = FlowFlags.ANY_CATEGORICAL; var commonConditional = 0; + // Track qualifying flows for field flag analysis + // A qualifying flow is a breaking flow or the last flow + var flows: Flow[] = []; for (let i = 0; i < numCases; ++i) { let case_ = cases[i]; let statements = case_.statements; @@ -2788,6 +2813,10 @@ export class Compiler extends DiagnosticEmitter { } commonConditional |= innerFlow.flags & FlowFlags.ANY_CONDITIONAL; + if (innerFlow.isAny(FlowFlags.BREAKS | FlowFlags.RETURNS) || isLast) { + flows.push(innerFlow); + } + // Switch back to the parent flow if (!terminates) this.performAutoreleases(innerFlow, stmts); innerFlow.unset( @@ -2801,7 +2830,28 @@ export class Compiler extends DiagnosticEmitter { outerFlow.popBreakLabel(); // If the switch has a default (guaranteed to handle any value), propagate common flags - if (defaultIndex >= 0) outerFlow.flags |= commonCategorical & ~FlowFlags.BREAKS; + if (defaultIndex >= 0) { + outerFlow.flags |= commonCategorical & ~FlowFlags.BREAKS; + + // If the switch: + // - has a default label + // - is located in the constructor + // - and has one or more qualifying flows + // analyze field flags + if (flows.length > 0 && this.currentFlow.actualFunction.is(CommonFlags.CONSTRUCTOR)) { + if (flows.length == 1) { + this.currentFlow.inheritFieldFlags(flows[0]); + } else { + for (let i = 0; i < flows.length; ++i) { + if (i < (flows.length - 1)) { + const left = flows[i]; + const right = flows[i + 1]; + this.currentFlow.mergeFieldFlags(left, right); + } + } + } + } + } outerFlow.flags |= commonConditional & ~FlowFlags.CONDITIONALLY_BREAKS; // TODO: what about local states? return currentBlock; @@ -5973,6 +6023,11 @@ export class Compiler extends DiagnosticEmitter { ); return module.unreachable(); } + + if (flow.actualFunction.is(CommonFlags.CONSTRUCTOR)) { + flow.setFieldFlag(fieldInstance.internalName, FieldFlags.INITIALIZED); + } + return this.makeFieldAssignment(fieldInstance, valueExpr, // FIXME: explicit type (currently fails due to missing null checking) @@ -8888,6 +8943,8 @@ export class Compiler extends DiagnosticEmitter { if (getExpressionType(expr) != NativeType.None) { // possibly IMM_DROPPED this.currentType = classInstance.type; // important because a super ctor could be called } + + this.checkFieldInitialization(classInstance); return expr; } @@ -10122,9 +10179,11 @@ export class Compiler extends DiagnosticEmitter { let fieldPrototype = field.prototype; let initializerNode = fieldPrototype.initializerNode; let parameterIndex = fieldPrototype.parameterIndex; - let initExpr: ExpressionRef; let typeNode = field.typeNode; if (typeNode) this.checkTypeSupported(fieldType, typeNode); + let initExpr: ExpressionRef = 0; + const isDefiniteAssigment = field.is(CommonFlags.DEFINITE_ASSIGNMENT); + let definitelyInitialized = false; // if declared as a constructor parameter, use its value if (parameterIndex >= 0) { @@ -10135,6 +10194,7 @@ export class Compiler extends DiagnosticEmitter { nativeFieldType ); if (fieldType.isManaged) initExpr = this.makeRetain(initExpr); + definitelyInitialized = true; // fall back to use initializer if present } else if (initializerNode) { @@ -10144,9 +10204,12 @@ export class Compiler extends DiagnosticEmitter { if (fieldType.isManaged && !this.skippedAutoreleases.has(initExpr)) { initExpr = this.makeRetain(initExpr); } - - // otherwise initialize with zero + definitelyInitialized = true; } else { + // otherwise initialize with default if marked as definite assigment + if (isDefiniteAssigment) { + definitelyInitialized = true; + } initExpr = this.makeZero(fieldType); } @@ -10158,6 +10221,12 @@ export class Compiler extends DiagnosticEmitter { field.memoryOffset ) ); + + if (definitelyInitialized) { + flow.setFieldFlag(field.internalName, FieldFlags.INITIALIZED); + } else { + flow.setFieldFlag(field.internalName, FieldFlags.NONE); + } } return stmts; } diff --git a/src/definitions.ts b/src/definitions.ts index c9834c174d..6595a7b0a1 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -57,7 +57,7 @@ export abstract class ExportsWalker { /** Constructs a new Element walker. */ constructor(program: Program, includePrivate: bool = false) { this.program = program; - this.includePrivate; + this.includePrivate = includePrivate; } /** Walks all elements and calls the respective handlers. */ diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 45988f1d65..e473094030 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -143,6 +143,7 @@ export enum DiagnosticCode { Expected_0_arguments_but_got_1 = 2554, Expected_at_least_0_arguments_but_got_1 = 2555, Expected_0_type_arguments_but_got_1 = 2558, + Property_0_has_no_initializer_and_is_not_definitely_assigned_in_constructor = 2564, A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums = 2651, Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration = 2673, Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration = 2674, @@ -297,6 +298,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 2554: return "Expected {0} arguments, but got {1}."; case 2555: return "Expected at least {0} arguments, but got {1}."; case 2558: return "Expected {0} type arguments, but got {1}."; + case 2564: return "Property {0} has no initializer and is not definitely assigned in constructor"; case 2651: return "A member initializer in a enum declaration cannot reference members declared after it, including members defined in other enums."; case 2673: return "Constructor of class '{0}' is private and only accessible within the class declaration."; case 2674: return "Constructor of class '{0}' is protected and only accessible within the class declaration."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 94ef96a447..dd6dcb3e75 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -139,6 +139,8 @@ "Expected {0} arguments, but got {1}.": 2554, "Expected at least {0} arguments, but got {1}.": 2555, "Expected {0} type arguments, but got {1}.": 2558, + "Property {0} has no initializer and is not definitely assigned in constructor": 2564, + "A member initializer in a enum declaration cannot reference members declared after it, including members defined in other enums.": 2651, "Constructor of class '{0}' is private and only accessible within the class declaration.": 2673, "Constructor of class '{0}' is protected and only accessible within the class declaration.": 2674, diff --git a/src/flow.ts b/src/flow.ts index 6296c8078f..860de10e6b 100644 --- a/src/flow.ts +++ b/src/flow.ts @@ -169,6 +169,12 @@ export enum LocalFlags { | CONDITIONALLY_RETAINED } +/** Flags indicating the current state of an instance field. */ +export enum FieldFlags { + NONE = 1, + INITIALIZED = 1 << 1, +} + /** Condition kinds. */ export const enum ConditionKind { /** Outcome of the condition is unknown */ @@ -183,41 +189,40 @@ export const enum ConditionKind { export class Flow { /** Parent flow. */ - parent: Flow | null; + parent: Flow | null = null; /** Flow flags indicating specific conditions. */ - flags: FlowFlags; + flags: FlowFlags = FlowFlags.NONE; /** Function this flow belongs to. */ - parentFunction: Function; + parentFunction!: Function; /** The label we break to when encountering a continue statement. */ - continueLabel: string | null; + continueLabel: string | null = null; /** The label we break to when encountering a break statement. */ - breakLabel: string | null; + breakLabel: string | null = null; /** The current return type. */ - returnType: Type; + returnType!: Type; /** The current contextual type arguments. */ - contextualTypeArguments: Map | null; + contextualTypeArguments: Map | null = null; /** Scoped local variables. */ scopedLocals: Map | null = null; /** Local flags. */ - localFlags: LocalFlags[]; + localFlags: LocalFlags[] = []; + /** Field flags. */ + fieldFlags: Map | null = null; /** Function being inlined, when inlining. */ - inlineFunction: Function | null; + inlineFunction: Function | null = null; /** The label we break to when encountering a return statement, when inlining. */ - inlineReturnLabel: string | null; + inlineReturnLabel: string | null = null; /** Creates the parent flow of the specified function. */ static create(parentFunction: Function): Flow { var flow = new Flow(); - flow.parent = null; - flow.flags = FlowFlags.NONE; flow.parentFunction = parentFunction; - flow.continueLabel = null; - flow.breakLabel = null; flow.returnType = parentFunction.signature.returnType; flow.contextualTypeArguments = parentFunction.contextualTypeArguments; - flow.localFlags = []; - flow.inlineFunction = null; - flow.inlineReturnLabel = null; + + if (flow.actualFunction.is(CommonFlags.CONSTRUCTOR)) { + flow.fieldFlags = new Map(); + } return flow; } @@ -228,6 +233,7 @@ export class Flow { flow.inlineReturnLabel = inlineFunction.internalName + "|inlined." + (inlineFunction.nextInlineId++).toString(); flow.returnType = inlineFunction.signature.returnType; flow.contextualTypeArguments = inlineFunction.contextualTypeArguments; + flow.fieldFlags = inlineFunction.flow.fieldFlags; return flow; } @@ -276,6 +282,11 @@ export class Flow { branch.localFlags = this.localFlags.slice(); branch.inlineFunction = this.inlineFunction; branch.inlineReturnLabel = this.inlineReturnLabel; + + if (branch.actualFunction.is(CommonFlags.CONSTRUCTOR)) { + branch.fieldFlags = new Map(); + } + return branch; } @@ -519,6 +530,26 @@ export class Flow { localFlags[index] = flags & ~flag; } + /** Associates the given flag with the given field name */ + setFieldFlag(name: string, flag: FieldFlags): void { + var fieldFlags = this.fieldFlags; + if (fieldFlags) { + const flags = fieldFlags.has(name) ? assert(fieldFlags.get(name)) : FieldFlags.NONE; + fieldFlags.set(name, flags | flag); + } + } + + /** Tests if the given field name and flag exist */ + isFieldFlag(name: string, flag: FieldFlags): bool { + const fieldFlags = this.fieldFlags; + + if (fieldFlags !== null && fieldFlags.has(name)) { + const flags = assert(fieldFlags.get(name)); + return (flags & flag) == flag; + } + return false; + } + /** Pushes a new break label to the stack, for example when entering a loop that one can `break` from. */ pushBreakLabel(): string { var parentFunction = this.parentFunction; @@ -806,6 +837,59 @@ export class Flow { thisLocalFlags[i] = newFlags; } } + + this.mergeFieldFlags(left, right); + } + + /** + * Merges the fields flags of the given flows + * into the current flow. Flags will only be merged + * if both flows definitely define the flags. + */ + mergeFieldFlags(left: Flow, right: Flow): void { + const leftFieldFlags = left.fieldFlags; + const rightFieldFlags = right.fieldFlags; + if ( + leftFieldFlags !== null && + rightFieldFlags !== null && + rightFieldFlags.size > 0 + ) { + const rightKeys = Map_keys(rightFieldFlags!); + const rightValues = Map_values(rightFieldFlags!); + + for (let i = 0, k = rightValues.length; i < k; ++i) { + const rightValue = unchecked(rightValues[i]); + const rightKey = unchecked(rightKeys[i]); + const leftValue = leftFieldFlags!.has(rightKey) ? assert(leftFieldFlags!.get(rightKey)) : FieldFlags.NONE; + + if (rightValue & FieldFlags.INITIALIZED) { + if (leftValue & FieldFlags.INITIALIZED) { + this.setFieldFlag(rightKey, FieldFlags.INITIALIZED); + } + } + } + } + } + + /** Inherits the fields flags of the given flow into the current flow */ + inheritFieldFlags(other: Flow): void { + const currentFieldFlags = this.fieldFlags; + const otherFieldFlags = other.fieldFlags; + if ( + currentFieldFlags !== null && + otherFieldFlags !== null + ) { + const otherKeys = Map_keys(otherFieldFlags!); + const otherValues = Map_values(otherFieldFlags!); + + for (let i = 0, k = otherValues.length; i < k; ++i) { + const key = otherKeys[i]; + const otherValue = otherValues[i]; + if (otherValue & FieldFlags.INITIALIZED) { + currentFieldFlags.set(key, FieldFlags.INITIALIZED); + } + } + } } /** Tests if the specified flows have differing local states. */ diff --git a/src/module.ts b/src/module.ts index 58c40425b6..7eefb9ca7e 100644 --- a/src/module.ts +++ b/src/module.ts @@ -468,8 +468,8 @@ export enum SIMDLoadOp { export class MemorySegment { - buffer: Uint8Array; - offset: i64; + buffer!: Uint8Array; + offset!: i64; static create(buffer: Uint8Array, offset: i64): MemorySegment { var segment = new MemorySegment(); @@ -481,9 +481,9 @@ export class MemorySegment { export class Module { - ref: ModuleRef; + ref!: ModuleRef; - private lit: usize; + private lit!: usize; static create(): Module { var module = new Module(); @@ -1896,8 +1896,8 @@ export function getEventResults(event: EventRef): NativeType { export class Relooper { - module: Module; - ref: RelooperRef; + module!: Module; + ref!: RelooperRef; static create(module: Module): Relooper { var relooper = new Relooper(); @@ -2141,9 +2141,9 @@ export function readString(ptr: usize): string | null { /** Result structure of {@link Module#toBinary}. */ export class BinaryModule { /** WebAssembly binary. */ - output: Uint8Array; + output!: Uint8Array; /** Source map, if generated. */ - sourceMap: string | null; + sourceMap: string | null = null; } /** Tests if an expression needs an explicit 'unreachable' when it is the terminating statement. */ diff --git a/src/parser.ts b/src/parser.ts index 0c0ac7d9c0..51bbb23d07 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -106,7 +106,7 @@ export class Parser extends DiagnosticEmitter { /** Optional handler to intercept comments while tokenizing. */ onComment: CommentHandler | null = null; /** Current file being parsed. */ - currentSource: Source; + currentSource!: Source; /** Dependency map **/ dependees: Map = new Map(); diff --git a/src/program.ts b/src/program.ts index ee92ef3131..2975c51ef9 100644 --- a/src/program.ts +++ b/src/program.ts @@ -448,68 +448,68 @@ export class Program extends DiagnosticEmitter { // standard references /** ArrayBufferView reference. */ - arrayBufferViewInstance: Class; + arrayBufferViewInstance!: Class; /** ArrayBuffer instance reference. */ - arrayBufferInstance: Class; + arrayBufferInstance!: Class; /** Array prototype reference. */ - arrayPrototype: ClassPrototype; + arrayPrototype!: ClassPrototype; /** Static array prototype reference. */ - staticArrayPrototype: ClassPrototype; + staticArrayPrototype!: ClassPrototype; /** Set prototype reference. */ - setPrototype: ClassPrototype; + setPrototype!: ClassPrototype; /** Map prototype reference. */ - mapPrototype: ClassPrototype; + mapPrototype!: ClassPrototype; /** Int8Array prototype. */ - i8ArrayPrototype: ClassPrototype; + i8ArrayPrototype!: ClassPrototype; /** Int16Array prototype. */ - i16ArrayPrototype: ClassPrototype; + i16ArrayPrototype!: ClassPrototype; /** Int32Array prototype. */ - i32ArrayPrototype: ClassPrototype; + i32ArrayPrototype!: ClassPrototype; /** Int64Array prototype. */ - i64ArrayPrototype: ClassPrototype; + i64ArrayPrototype!: ClassPrototype; /** Uint8Array prototype. */ - u8ArrayPrototype: ClassPrototype; + u8ArrayPrototype!: ClassPrototype; /** Uint8ClampedArray prototype. */ - u8ClampedArrayPrototype: ClassPrototype; + u8ClampedArrayPrototype!: ClassPrototype; /** Uint16Array prototype. */ - u16ArrayPrototype: ClassPrototype; + u16ArrayPrototype!: ClassPrototype; /** Uint32Array prototype. */ - u32ArrayPrototype: ClassPrototype; + u32ArrayPrototype!: ClassPrototype; /** Uint64Array prototype. */ - u64ArrayPrototype: ClassPrototype; + u64ArrayPrototype!: ClassPrototype; /** Float32Array prototype. */ - f32ArrayPrototype: ClassPrototype; + f32ArrayPrototype!: ClassPrototype; /** Float64Array prototype. */ - f64ArrayPrototype: ClassPrototype; + f64ArrayPrototype!: ClassPrototype; /** String instance reference. */ - stringInstance: Class; + stringInstance!: Class; /** Abort function reference, if not explicitly disabled. */ - abortInstance: Function | null; + abortInstance: Function | null = null; // runtime references /** RT `__alloc(size: usize, id: u32): usize` */ - allocInstance: Function; + allocInstance!: Function; /** RT `__realloc(ptr: usize, newSize: usize): usize` */ - reallocInstance: Function; + reallocInstance!: Function; /** RT `__free(ptr: usize): void` */ - freeInstance: Function; + freeInstance!: Function; /** RT `__retain(ptr: usize): usize` */ - retainInstance: Function; + retainInstance!: Function; /** RT `__release(ptr: usize): void` */ - releaseInstance: Function; + releaseInstance!: Function; /** RT `__collect(): void` */ - collectInstance: Function; + collectInstance!: Function; /** RT `__visit(ptr: usize, cookie: u32): void` */ - visitInstance: Function; + visitInstance!: Function; /** RT `__typeinfo(id: u32): RTTIFlags` */ - typeinfoInstance: Function; + typeinfoInstance!: Function; /** RT `__instanceof(ptr: usize, superId: u32): bool` */ - instanceofInstance: Function; + instanceofInstance!: Function; /** RT `__allocBuffer(size: usize, id: u32, data: usize = 0): usize` */ - allocBufferInstance: Function; + allocBufferInstance!: Function; /** RT `__allocArray(length: i32, alignLog2: usize, id: u32, data: usize = 0): usize` */ - allocArrayInstance: Function; + allocArrayInstance!: Function; /** Next class id. */ nextClassId: u32 = 0; @@ -2699,9 +2699,9 @@ export abstract class VariableLikeElement extends TypedElement { /** Constant value kind. */ constantValueKind: ConstantValueKind = ConstantValueKind.NONE; /** Constant integer value, if applicable. */ - constantIntegerValue: i64; + constantIntegerValue!: i64; /** Constant float value, if applicable. */ - constantFloatValue: f64; + constantFloatValue!: f64; /** Constructs a new variable-like element. */ protected constructor( diff --git a/src/tokenizer.ts b/src/tokenizer.ts index c0235bd9cb..78ae4940ef 100644 --- a/src/tokenizer.ts +++ b/src/tokenizer.ts @@ -1567,11 +1567,11 @@ export class Tokenizer extends DiagnosticEmitter { /** Tokenizer state as returned by {@link Tokenizer#mark} and consumed by {@link Tokenizer#reset}. */ export class State { /** Current position. */ - pos: i32; + pos!: i32; /** Current token. */ - token: Token; + token!: Token; /** Current token's position. */ - tokenPos: i32; + tokenPos!: i32; } // Reusable state object to reduce allocations diff --git a/std/assembly/map.ts b/std/assembly/map.ts index 0e82f9f96c..edafc459f0 100644 --- a/std/assembly/map.ts +++ b/std/assembly/map.ts @@ -64,14 +64,14 @@ function ENTRY_SIZE(): usize { export class Map { // buckets holding references to the respective first entry within - private buckets: ArrayBuffer; // usize[bucketsMask + 1] - private bucketsMask: u32; + private buckets!: ArrayBuffer; // usize[bucketsMask + 1] + private bucketsMask!: u32; // entries in insertion order - private entries: ArrayBuffer; // MapEntry[entriesCapacity] - private entriesCapacity: i32; - private entriesOffset: i32; - private entriesCount: i32; + private entries!: ArrayBuffer; // MapEntry[entriesCapacity] + private entriesCapacity!: i32; + private entriesOffset!: i32; + private entriesCount!: i32; get size(): i32 { return this.entriesCount; } diff --git a/std/assembly/set.ts b/std/assembly/set.ts index ca2a1ca299..a5e0606fa4 100644 --- a/std/assembly/set.ts +++ b/std/assembly/set.ts @@ -61,14 +61,14 @@ function ENTRY_SIZE(): usize { export class Set { // buckets holding references to the respective first entry within - private buckets: ArrayBuffer; // usize[bucketsMask + 1] - private bucketsMask: u32; + private buckets!: ArrayBuffer; // usize[bucketsMask + 1] + private bucketsMask!: u32; // entries in insertion order - private entries: ArrayBuffer; // SetEntry[entriesCapacity] - private entriesCapacity: i32; - private entriesOffset: i32; - private entriesCount: i32; + private entries!: ArrayBuffer; // SetEntry[entriesCapacity] + private entriesCapacity!: i32; + private entriesOffset!: i32; + private entriesCount!: i32; get size(): i32 { return this.entriesCount; } diff --git a/std/assembly/shared/typeinfo.ts b/std/assembly/shared/typeinfo.ts index eee22a614c..18914cac71 100644 --- a/std/assembly/shared/typeinfo.ts +++ b/std/assembly/shared/typeinfo.ts @@ -16,9 +16,9 @@ @unmanaged export class Typeinfo { /** Flags describing the shape of this class type. */ - flags: TypeinfoFlags; + flags!: TypeinfoFlags; /** Base class id or `0` if none. */ - base: u32; + base!: u32; } /** Runtime type information flags. */ diff --git a/tests/compiler/constructor.ts b/tests/compiler/constructor.ts index d24a43402f..02b3fa6fdf 100644 --- a/tests/compiler/constructor.ts +++ b/tests/compiler/constructor.ts @@ -15,7 +15,7 @@ var emptyCtorWithFieldInit = new EmptyCtorWithFieldInit(); // trailing conditional allocate with field initialized to zero class EmptyCtorWithFieldNoInit { - a: i32; + a!: i32; constructor() {} } @@ -36,7 +36,7 @@ var justFieldInit = new JustFieldInit(); // direct allocate with field initialized to zero class JustFieldNoInit { - a: i32; + a!: i32; } var justFieldNoInit = new JustFieldNoInit(); diff --git a/tests/compiler/extends-baseaggregate.ts b/tests/compiler/extends-baseaggregate.ts index dfc2522675..2fc89ffe17 100644 --- a/tests/compiler/extends-baseaggregate.ts +++ b/tests/compiler/extends-baseaggregate.ts @@ -1,17 +1,17 @@ class A1 { - private padding0: f64; - private padding1: f64; - private c1: C1; + private padding0!: f64; + private padding1!: f64; + private c1!: C1; } class A2 extends A1 { } class B1 { - private a1: A1; + private a1!: A1; } class C1 { - private a2: A2; + private a2!: A2; } const poolB: B1[] = []; diff --git a/tests/compiler/resolve-access.ts b/tests/compiler/resolve-access.ts index 9519101fb2..cefbce29c3 100644 --- a/tests/compiler/resolve-access.ts +++ b/tests/compiler/resolve-access.ts @@ -4,7 +4,7 @@ export function arrayAccess(): string { } class Container { - foo: u64; + foo!: u64; toU32(): u32 { return this.foo as u32; diff --git a/tests/compiler/retain-release-sanity.ts b/tests/compiler/retain-release-sanity.ts index b46f26d95b..c5a53c7f90 100644 --- a/tests/compiler/retain-release-sanity.ts +++ b/tests/compiler/retain-release-sanity.ts @@ -21,10 +21,10 @@ } class A { - b: B; + b!: B; } class B { - a: A; + a!: A; } { diff --git a/tests/compiler/retain-release.ts b/tests/compiler/retain-release.ts index bc4177571c..e7447e4019 100644 --- a/tests/compiler/retain-release.ts +++ b/tests/compiler/retain-release.ts @@ -115,7 +115,7 @@ export function assignGlobal(): void { glo = /* __retainRelease( */ REF /* , glo) */; } -class Target { fld: Ref; } +class Target { fld!: Ref; } var TARGET = new Target(); diff --git a/tests/compiler/strict-init.json b/tests/compiler/strict-init.json new file mode 100644 index 0000000000..f20de54bf1 --- /dev/null +++ b/tests/compiler/strict-init.json @@ -0,0 +1,14 @@ +{ + "asc_flags": [ + "--runtime none" + ], + "stderr": [ + "TS2564: Property inlinedProp has no initializer and is not definitely assigned in constructor", + "TS2564: Property b has no initializer and is not definitely assigned in constructor", + "TS2564: Property fieldLeft has no initializer and is not definitely assigned in constructor", + "TS2564: Property fieldRight has no initializer and is not definitely assigned in constructor", + "TS2564: Property p has no initializer and is not definitely assigned in constructor", + "TS2564: Property switchNotInitInDefault has no initializer and is not definitely assigned in constructor", + "TS2564: Property switchNoDefault has no initializer and is not definitely assigned in constructor" + ] +} diff --git a/tests/compiler/strict-init.optimized.wat b/tests/compiler/strict-init.optimized.wat new file mode 100644 index 0000000000..be666c43cd --- /dev/null +++ b/tests/compiler/strict-init.optimized.wat @@ -0,0 +1,108 @@ +(module + (type $none_=>_none (func)) + (type $i32_=>_none (func (param i32))) + (type $i32_i32_i32_i32_=>_none (func (param i32 i32 i32 i32))) + (type $none_=>_i32 (func (result i32))) + (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) + (memory $0 1) + (data (i32.const 16) "\1c\00\00\00\01\00\00\00\01\00\00\00\1c\00\00\00s\00t\00r\00i\00c\00t\00-\00i\00n\00i\00t\00.\00t\00s") + (global $~lib/rt/stub/startOffset (mut i32) (i32.const 0)) + (global $~lib/rt/stub/offset (mut i32) (i32.const 0)) + (export "memory" (memory $0)) + (start $~start) + (func $~lib/rt/stub/maybeGrowMemory (; 1 ;) (param $0 i32) + (local $1 i32) + (local $2 i32) + local.get $0 + memory.size + local.tee $2 + i32.const 16 + i32.shl + local.tee $1 + i32.gt_u + if + local.get $2 + local.get $0 + local.get $1 + i32.sub + i32.const 65535 + i32.add + i32.const -65536 + i32.and + i32.const 16 + i32.shr_u + local.tee $1 + local.get $2 + local.get $1 + i32.gt_s + select + memory.grow + i32.const 0 + i32.lt_s + if + local.get $1 + memory.grow + i32.const 0 + i32.lt_s + if + unreachable + end + end + end + local.get $0 + global.set $~lib/rt/stub/offset + ) + (func $~lib/rt/stub/__alloc (; 2 ;) (result i32) + (local $0 i32) + (local $1 i32) + global.get $~lib/rt/stub/offset + i32.const 16 + i32.add + local.tee $1 + i32.const 16 + i32.add + call $~lib/rt/stub/maybeGrowMemory + local.get $1 + i32.const 16 + i32.sub + local.tee $0 + i32.const 16 + i32.store + local.get $0 + i32.const 1 + i32.store offset=4 + local.get $0 + i32.const 3 + i32.store offset=8 + local.get $0 + i32.const 4 + i32.store offset=12 + local.get $1 + ) + (func $start:strict-init (; 3 ;) + (local $0 i32) + i32.const 64 + global.set $~lib/rt/stub/startOffset + i32.const 64 + global.set $~lib/rt/stub/offset + call $~lib/rt/stub/__alloc + local.tee $0 + i32.const 1 + i32.store + local.get $0 + i32.load + i32.const 1 + i32.ne + if + i32.const 0 + i32.const 32 + i32.const 5 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + ) + (func $~start (; 4 ;) + call $start:strict-init + ) +) diff --git a/tests/compiler/strict-init.ts b/tests/compiler/strict-init.ts new file mode 100644 index 0000000000..e1772f6ed2 --- /dev/null +++ b/tests/compiler/strict-init.ts @@ -0,0 +1,183 @@ +// OK - Field with explicit initializer +export class WithInitializer { + public a: i32 = 1; + some: string | null = null; +} + +// ERR - Field b not initialized +export class WithoutInitializer { + b: f64; + constructor() {} +} + +// OK - Field as constructor param +class Param {} +export class FieldAsConstructorParam { + constructor(public val: Param) {} +} + +// OK - Field initialized in constructor +export class ExplicitConstructorInit { + p: Param; + constructor(a: Param) { + this.p = a; + } +} + +// ERR - Left block empty +export class EmptyLeftBlock { + fieldLeft: i32; + + constructor(x: i32) { + if ((x + 5) == 6) { + } else { + this.fieldLeft = 7; + } + } +} + +// ERR - Right block empty +export class EmptyRightBlock { + fieldRight: i32; + + constructor(x: i32) { + if ((x + 5) == 6) { + this.fieldRight = 7; + } else { + } + } +} + +// ERR - Indefinite +export class NonDefiniteIf { + p: f64; + constructor(a: i32) { + if ((a % 2) == 0) { + this.p = 1.0; + } else if ((a * 2) == 10) { + this.p = 0.0; + } + } +} + +// OK - All branches covered +export class DefiniteIf { + definite: i32; + + constructor(a: i32) { + if ((a % 2) == 0) { + this.definite = 1; + } else if ((a * 2) == 10) { + this.definite = 0; + } else if ((a / 2) == 1) { + this.definite = 8; + } else { + this.definite = 0; + } + } +} + + +// ERR +export class Inlined { + inlinedProp: string | null; +} + +new Inlined(); + +// OK - inherited fields +class ElementKind {} + +export abstract class Y { + inherited: string | null = "the inherited string"; +} + +export abstract class X extends Y { + ax: string[] | null; + protected constructor(public kind: ElementKind, other: ElementKind | null) { + super(); + + if (other) { + this.ax = ["string"]; + } else { + this.ax = ["string", "string"]; + } + } +} + +// OK +export class SwitchFallback { + switchFallback: i32; + constructor(some: i32) { + switch (some) { + case 1: + case 2: + case 3: + default: + this.switchFallback = 6; + break; + } + } +} + +// ERR +export class SwitchWithNonInitInDefault { + switchNotInitInDefault: i32; + + constructor(some: i32) { + switch (some) { + case 0: + case 1: + this.switchNotInitInDefault = 1; + break; + case 3: + this.switchNotInitInDefault = 5; + default: + const x = 6; + break; + } + } +} + +// OK +export class SwitchDefinitelyInit { + switchDefinitelyInit: i32; + constructor(some: i32) { + switch(some) { + case 0: + this.switchDefinitelyInit = 0; + break; + case 1: + this.switchDefinitelyInit = 1; + break; + case 3: + this.switchDefinitelyInit = 5; + default: + this.switchDefinitelyInit = 10; + break; + } + } +} + +// ERR +export class SwitchNoDefault { + switchNoDefault: i32; + + constructor(some: i32) { + switch(some) { + case 1: + this.switchNoDefault = 4; + } + } +} + +export class SwitchOnlyDefault { + switchOnlyDefault: i32; + + constructor(some: i32) { + switch (some) { + default: + this.switchOnlyDefault = 0; + } + } +} diff --git a/tests/compiler/strict-init.untouched.wat b/tests/compiler/strict-init.untouched.wat new file mode 100644 index 0000000000..b75c2cf028 --- /dev/null +++ b/tests/compiler/strict-init.untouched.wat @@ -0,0 +1,177 @@ +(module + (type $none_=>_none (func)) + (type $i32_=>_none (func (param i32))) + (type $i32_=>_i32 (func (param i32) (result i32))) + (type $i32_i32_i32_i32_=>_none (func (param i32 i32 i32 i32))) + (type $i32_i32_=>_i32 (func (param i32 i32) (result i32))) + (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) + (memory $0 1) + (data (i32.const 16) "\1c\00\00\00\01\00\00\00\01\00\00\00\1c\00\00\00s\00t\00r\00i\00c\00t\00-\00i\00n\00i\00t\00.\00t\00s\00") + (table $0 1 funcref) + (global $~lib/rt/stub/startOffset (mut i32) (i32.const 0)) + (global $~lib/rt/stub/offset (mut i32) (i32.const 0)) + (global $~lib/heap/__heap_base i32 (i32.const 60)) + (export "memory" (memory $0)) + (start $~start) + (func $~lib/rt/stub/maybeGrowMemory (; 1 ;) (param $0 i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + memory.size + local.set $1 + local.get $1 + i32.const 16 + i32.shl + local.set $2 + local.get $0 + local.get $2 + i32.gt_u + if + local.get $0 + local.get $2 + i32.sub + i32.const 65535 + i32.add + i32.const 65535 + i32.const -1 + i32.xor + i32.and + i32.const 16 + i32.shr_u + local.set $3 + local.get $1 + local.tee $4 + local.get $3 + local.tee $5 + local.get $4 + local.get $5 + i32.gt_s + select + local.set $4 + local.get $4 + memory.grow + i32.const 0 + i32.lt_s + if + local.get $3 + memory.grow + i32.const 0 + i32.lt_s + if + unreachable + end + end + end + local.get $0 + global.set $~lib/rt/stub/offset + ) + (func $~lib/rt/stub/__alloc (; 2 ;) (param $0 i32) (param $1 i32) (result i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (local $6 i32) + local.get $0 + i32.const 1073741808 + i32.gt_u + if + unreachable + end + global.get $~lib/rt/stub/offset + i32.const 16 + i32.add + local.set $2 + local.get $0 + i32.const 15 + i32.add + i32.const 15 + i32.const -1 + i32.xor + i32.and + local.tee $3 + i32.const 16 + local.tee $4 + local.get $3 + local.get $4 + i32.gt_u + select + local.set $5 + local.get $2 + local.get $5 + i32.add + call $~lib/rt/stub/maybeGrowMemory + local.get $2 + i32.const 16 + i32.sub + local.set $6 + local.get $6 + local.get $5 + i32.store + local.get $6 + i32.const 1 + i32.store offset=4 + local.get $6 + local.get $1 + i32.store offset=8 + local.get $6 + local.get $0 + i32.store offset=12 + local.get $2 + ) + (func $~lib/rt/stub/__retain (; 3 ;) (param $0 i32) (result i32) + local.get $0 + ) + (func $strict-init/WithInitializer#constructor (; 4 ;) (param $0 i32) (result i32) + local.get $0 + i32.eqz + if + i32.const 4 + i32.const 3 + call $~lib/rt/stub/__alloc + call $~lib/rt/stub/__retain + local.set $0 + end + local.get $0 + i32.const 1 + i32.store + local.get $0 + ) + (func $~lib/rt/stub/__release (; 5 ;) (param $0 i32) + nop + ) + (func $start:strict-init (; 6 ;) + (local $0 i32) + global.get $~lib/heap/__heap_base + i32.const 15 + i32.add + i32.const 15 + i32.const -1 + i32.xor + i32.and + global.set $~lib/rt/stub/startOffset + global.get $~lib/rt/stub/startOffset + global.set $~lib/rt/stub/offset + i32.const 0 + call $strict-init/WithInitializer#constructor + local.tee $0 + i32.load + i32.const 1 + i32.eq + i32.eqz + if + i32.const 0 + i32.const 32 + i32.const 5 + i32.const 0 + call $~lib/builtins/abort + unreachable + end + local.get $0 + call $~lib/rt/stub/__release + ) + (func $~start (; 7 ;) + call $start:strict-init + ) +) diff --git a/tsconfig-base.json b/tsconfig-base.json index 415a5fb820..49a6f9a839 100644 --- a/tsconfig-base.json +++ b/tsconfig-base.json @@ -6,6 +6,7 @@ "noImplicitThis": true, "noEmitOnError": true, "strictNullChecks": true, + "strictPropertyInitialization": true, "experimentalDecorators": true } }