Skip to content

Commit b0d9123

Browse files
committed
Added error messages
1 parent 2aac7bd commit b0d9123

File tree

10 files changed

+116
-41
lines changed

10 files changed

+116
-41
lines changed

src/ast.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,7 +1103,7 @@ export abstract class TypeNode extends Node {
11031103
}
11041104
return false;
11051105
}
1106-
1106+
11071107
/** Method for determine type equality. */
11081108
abstract equals(node: TypeNode): bool;
11091109

@@ -1171,7 +1171,7 @@ export class FunctionTypeNode extends TypeNode {
11711171
}
11721172

11731173
get parameterTypes(): TypeNode[] {
1174-
return this.parameters.map(param => param.type);
1174+
return this.parameters.map((param: ParameterNode): TypeNode => param.type);
11751175
}
11761176
}
11771177

src/compiler.ts

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2944,7 +2944,60 @@ export class Compiler extends DiagnosticEmitter {
29442944

29452945
if (this.currentFlow.isNonnull(expr, fromType)) fromType = fromType.nonNullableType;
29462946

2947-
if (!fromType.isAssignableTo(toType)) {
2947+
// Check if a converting to an Interface
2948+
if (toType.isInterface && !toType.isFunction) {
2949+
if (!fromType.isManaged || fromType.classReference == null) {
2950+
this.error(
2951+
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
2952+
reportNode.range,
2953+
fromType.toString(),
2954+
toType.toString());
2955+
}else {
2956+
const _interface = (<Interface>toType.classReference!);
2957+
let _class = fromType.classReference!;
2958+
let incorrectMethods = _interface.checkClass(_class);
2959+
if (incorrectMethods.length == 0) {
2960+
_interface.implementers.add(fromType.classReference!);
2961+
} else {
2962+
this.error(
2963+
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
2964+
reportNode.range,
2965+
fromType.toString(),
2966+
toType.toString());
2967+
let missingMethods = false;
2968+
// tslint:disable-next-line: as-types
2969+
incorrectMethods.forEach(ifunc => {
2970+
if (_class.members == null || !_class.members.has(ifunc.name)) {
2971+
if (!missingMethods) {
2972+
missingMethods = true;
2973+
this.error(
2974+
DiagnosticCode.Class_0_incorrectly_implements_interface_1,
2975+
_class.identifierNode.range,
2976+
fromType.toString(),
2977+
toType.toString()
2978+
);
2979+
}
2980+
this.error(
2981+
DiagnosticCode.Property_0_is_missing_in_type_1_but_required_in_type_2,
2982+
_class.identifierNode.range,
2983+
ifunc.name,
2984+
fromType.toString(),
2985+
toType.toString()
2986+
);
2987+
} else {
2988+
let otherFunc = _class.members.get(ifunc.name)!;
2989+
this.error(
2990+
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
2991+
otherFunc.identifierNode.range,
2992+
_class.name + "." + otherFunc.name,
2993+
_interface.name + "." + ifunc.name);
2994+
}
2995+
});
2996+
}
2997+
2998+
}
2999+
}
3000+
else if (!fromType.isAssignableTo(toType)) {
29483001
if (!explicit) {
29493002
if (fromType.nonNullableType == toType) {
29503003
this.error(
@@ -2959,14 +3012,6 @@ export class Compiler extends DiagnosticEmitter {
29593012
}
29603013
}
29613014
}
2962-
// Check if a converting to an Interface
2963-
if (toType.is(TypeFlags.REFERENCE) && !toType.isFunction) {
2964-
assert(toType.classReference != null && fromType.classReference != null);
2965-
if (toType.classReference!.kind == ElementKind.INTERFACE) {
2966-
(<Interface>toType.classReference!).implementers.add(fromType.classReference!);
2967-
}
2968-
2969-
}
29703015

29713016
if (fromType.is(TypeFlags.FLOAT)) {
29723017

@@ -9246,7 +9291,7 @@ export class Compiler extends DiagnosticEmitter {
92469291

92479292
const callIndirect = module.call_indirect(
92489293
callVirtual,
9249-
func.localsByIndex.map<number>(local =>
9294+
func.localsByIndex.map<usize>((local: Local): usize =>
92509295
module.local_get(local.index, local.type.toNativeType())
92519296
),
92529297
Signature.makeSignatureString(
@@ -9268,7 +9313,6 @@ export class Compiler extends DiagnosticEmitter {
92689313

92699314
// helpers
92709315

9271-
92729316
function mangleImportName(
92739317
element: Element,
92749318
declaration: DeclarationStatement
@@ -9345,4 +9389,3 @@ export function flatten(module: Module, stmts: ExpressionRef[], type: NativeType
93459389
: type
93469390
);
93479391
}
9348-

src/diagnosticMessages.generated.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export enum DiagnosticCode {
118118
Multiple_constructor_implementations_are_not_allowed = 2392,
119119
Duplicate_function_implementation = 2393,
120120
Individual_declarations_in_merged_declaration_0_must_be_all_exported_or_all_local = 2395,
121+
Class_0_incorrectly_implements_interface_1 = 2420,
121122
A_namespace_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged = 2434,
122123
Type_0_has_no_property_1 = 2460,
123124
The_0_operator_cannot_be_applied_to_type_1 = 2469,
@@ -136,6 +137,7 @@ export enum DiagnosticCode {
136137
Namespace_0_has_no_exported_member_1 = 2694,
137138
Required_type_parameters_may_not_follow_optional_type_parameters = 2706,
138139
Duplicate_property_0 = 2718,
140+
Property_0_is_missing_in_type_1_but_required_in_type_2 = 2741,
139141
Type_0_has_no_call_signatures = 2757,
140142
File_0_not_found = 6054,
141143
Numeric_separators_are_not_allowed_here = 6188,
@@ -258,6 +260,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
258260
case 2392: return "Multiple constructor implementations are not allowed.";
259261
case 2393: return "Duplicate function implementation.";
260262
case 2395: return "Individual declarations in merged declaration '{0}' must be all exported or all local.";
263+
case 2420: return "Class '{0}' incorrectly implements interface '{1}'.";
261264
case 2434: return "A namespace declaration cannot be located prior to a class or function with which it is merged.";
262265
case 2460: return "Type '{0}' has no property '{1}'.";
263266
case 2469: return "The '{0}' operator cannot be applied to type '{1}'.";
@@ -276,6 +279,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
276279
case 2694: return "Namespace '{0}' has no exported member '{1}'.";
277280
case 2706: return "Required type parameters may not follow optional type parameters.";
278281
case 2718: return "Duplicate property '{0}'.";
282+
case 2741: return "Property '{0}' is missing in type '{1}' but required in type '{2}'.";
279283
case 2757: return "Type '{0}' has no call signatures.";
280284
case 6054: return "File '{0}' not found.";
281285
case 6188: return "Numeric separators are not allowed here.";

src/diagnosticMessages.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"Multiple constructor implementations are not allowed.": 2392,
113113
"Duplicate function implementation.": 2393,
114114
"Individual declarations in merged declaration '{0}' must be all exported or all local.": 2395,
115+
"Class '{0}' incorrectly implements interface '{1}'.": 2420,
115116
"A namespace declaration cannot be located prior to a class or function with which it is merged.": 2434,
116117
"Type '{0}' has no property '{1}'.": 2460,
117118
"The '{0}' operator cannot be applied to type '{1}'.": 2469,
@@ -130,6 +131,7 @@
130131
"Namespace '{0}' has no exported member '{1}'.": 2694,
131132
"Required type parameters may not follow optional type parameters.": 2706,
132133
"Duplicate property '{0}'.": 2718,
134+
"Property '{0}' is missing in type '{1}' but required in type '{2}'.": 2741,
133135
"Type '{0}' has no call signatures.": 2757,
134136

135137
"File '{0}' not found.": 6054,

src/program.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3160,7 +3160,7 @@ export class ClassPrototype extends DeclaredElement {
31603160
/** Instance Methods */
31613161
get instanceMethods(): FunctionPrototype[] {
31623162
if (!this.instanceMembers) return [];
3163-
return <FunctionPrototype[]> filter(this.instanceMembers.values(), mem => mem.kind == ElementKind.FUNCTION_PROTOTYPE);
3163+
return <FunctionPrototype[]> filter<FunctionPrototype>(this.instanceMembers.values(), (mem: Element): bool => mem.kind == ElementKind.FUNCTION_PROTOTYPE);
31643164
}
31653165

31663166
constructor(
@@ -3386,8 +3386,8 @@ export class Class extends TypedElement {
33863386
/** Tests if a value of this class type is assignable to a target of the specified class type. */
33873387
isAssignableTo(target: Class): bool {
33883388
var current: Class | null = this;
3389-
if (target.kind == ElementKind.INTERFACE) {
3390-
return (<Interface>target).checkClass(this);
3389+
if (target.kind == ElementKind.INTERFACE) {
3390+
return true;
33913391
}
33923392
do if (current == target) return true;
33933393
while (current = current.base);
@@ -3650,15 +3650,16 @@ export class Interface extends Class { // FIXME
36503650

36513651
get methods(): FunctionPrototype[] {
36523652
if (this.members == null) return [];
3653-
return <FunctionPrototype[]>filter(this.members.values(), v => v.kind == ElementKind.FUNCTION_PROTOTYPE)
3653+
return <FunctionPrototype[]>filter(this.members.values(),
3654+
(v: DeclaredElement): bool => v.kind == ElementKind.FUNCTION_PROTOTYPE);
36543655
}
36553656

36563657
get methodInstances(): Function[] {
36573658
var funcs: Function[] = [];
36583659
for (let func of this.methods) {
36593660
if (func.instances == null) continue;
36603661
map(func.instances.values(), (func: Function): void => {
3661-
if (funcs.findIndex(f => f.signature.id == func.signature.id) < 0) {
3662+
if (funcs.findIndex((f: Function): bool => f.signature.id == func.signature.id) < 0) {
36623663
funcs.push(func);
36633664
}
36643665
});
@@ -3670,12 +3671,13 @@ export class Interface extends Class { // FIXME
36703671
this.implementers.add(_class);
36713672
}
36723673

3673-
checkClass(_class: Class): bool {
3674-
return this.methods.reduce<bool>((acc, ifunc) => {
3674+
checkClass(_class: Class): FunctionPrototype[] {
3675+
const res = [];
3676+
for (const ifunc of this.methods) {
36753677
let func = this.getFunc(_class, ifunc);
3676-
if (func == null) return false;
3677-
return ifunc.equals(func) && acc;
3678-
}, true);
3678+
if (func == null || !(ifunc.equals(func))) res.push(ifunc);
3679+
}
3680+
return res;
36793681
}
36803682

36813683
private getFunc(_class: Class, ifunc: FunctionPrototype): FunctionPrototype | null {
@@ -3685,8 +3687,8 @@ export class Interface extends Class { // FIXME
36853687

36863688
getFuncImplementations(ifunc: Function): FunctionPrototype[] {
36873689
return <FunctionPrototype[]> map(this.implementers,
3688-
_class => this.getFunc(_class, ifunc.prototype))
3689-
.filter(func => func != null);
3690+
(_class: Class): FunctionPrototype | null => this.getFunc(_class, ifunc.prototype))
3691+
.filter((func: FunctionPrototype): bool => func != null);
36903692
}
36913693

36923694
get methodsToCompile(): FunctionPrototype[] {
@@ -3696,8 +3698,6 @@ export class Interface extends Class { // FIXME
36963698
}
36973699
return funcs;
36983700
}
3699-
3700-
37013701
}
37023702

37033703
/** Registers a concrete element with a program. */

src/resolver.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2713,8 +2713,8 @@ export class Resolver extends DiagnosticEmitter {
27132713
// Construct the instance and remember that it has been resolved already
27142714
var nameInclTypeParamters = prototype.name;
27152715
if (instanceKey.length) nameInclTypeParamters += "<" + instanceKey + ">";
2716-
instance = <Class>((prototype instanceof InterfacePrototype) ?
2717-
new Interface(nameInclTypeParamters, prototype, typeArguments, baseClass) :
2716+
instance = <Class>((prototype instanceof InterfacePrototype) ?
2717+
new Interface(nameInclTypeParamters, prototype, typeArguments, baseClass) :
27182718
new Class(nameInclTypeParamters, prototype, typeArguments, baseClass));
27192719
instance.contextualTypeArguments = ctxTypes;
27202720
prototype.setResolvedInstance(instanceKey, instance);

src/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
Class,
88
FunctionTarget,
99
Program,
10-
DecoratorFlags
10+
DecoratorFlags,
11+
ElementKind
1112
} from "./program";
1213

1314
import {
@@ -178,6 +179,10 @@ export class Type {
178179
return signature != null && this.is(TypeFlags.REFERENCE);
179180
}
180181

182+
get isInterface(): bool {
183+
return this.classReference != null && this.classReference.kind == ElementKind.INTERFACE;
184+
}
185+
181186
/** Computes the sign-extending shift in the target type. */
182187
computeSmallIntegerShift(targetType: Type): u32 {
183188
return targetType.size - this.size;

tests/compiler/interface-fail.json

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
{
22
"asc_flags": [
33
"--runtime none"
4-
]
5-
}
4+
],
5+
"stderr": [
6+
"TS2322: Type 'interface-fail/BadFoo' is not assignable to type 'interface-fail/IFoo'.",
7+
"const aBadFoo: IFoo = new BadFoo();",
8+
"TS2420: Class 'interface-fail/BadFoo' incorrectly implements interface 'interface-fail/IFoo'.",
9+
"class BadFoo implements IFoo {",
10+
"TS2741: Property 'foo' is missing in type 'interface-fail/BadFoo' but required in type 'interface-fail/IFoo'.",
11+
"class BadFoo implements IFoo {",
12+
"TS2322: Type 'BadFoo.faa' is not assignable to type 'IFoo.faa'.",
13+
"faa(i: i32, i2: i32): i32 {",
14+
"TS2322: Type 'interface-fail/AnotherBadFoo' is not assignable to type 'interface-fail/IFoo'.",
15+
"const anotherBadFoo: IFoo = new AnotherBadFoo();",
16+
"TS2420: Class 'interface-fail/AnotherBadFoo' incorrectly implements interface 'interface-fail/IFoo'.",
17+
"class AnotherBadFoo {}",
18+
"TS2741: Property 'foo' is missing in type 'interface-fail/AnotherBadFoo' but required in type 'interface-fail/IFoo'.",
19+
"class AnotherBadFoo {}",
20+
"TS2741: Property 'faa' is missing in type 'interface-fail/AnotherBadFoo' but required in type 'interface-fail/IFoo'.",
21+
"class AnotherBadFoo {}",
22+
"TS2322: Type 'i32' is not assignable to type 'interface-fail/IFoo'.",
23+
"const intFoo: IFoo = <i32>5;"]
24+
}

tests/compiler/interface-fail.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11

22
interface IFoo {
33
foo(i: i32): i32;
4+
faa(i: i32, i2: i32): string;
45
}
56

67
class BadFoo implements IFoo {
7-
i: i32 = 41;
8-
9-
foo(i: i32): string {
10-
return "hello";
11-
}
128

139
faa(i: i32, i2: i32): i32 {
1410
return i + i2;
1511
}
1612

1713
}
1814

15+
class AnotherBadFoo {}
1916

20-
const aFoo: IFoo = new BadFoo();
21-
22-
17+
const aBadFoo: IFoo = new BadFoo();
18+
const anotherBadFoo: IFoo = new AnotherBadFoo();
19+
const intFoo: IFoo = <i32>5;

tests/compiler/interface.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"asc_flags": [
3+
"--runtime none"
4+
]
5+
}

0 commit comments

Comments
 (0)