Improve type checking in trait instances
93e3225c9203988faa658014fb05e426a7368141aebf6bb9cdc4d11570fb784e
Method signatures were not properly checked.
1 parent
ae7f9152
lib/std/lang/resolver.rad
+36 -10
| 447 | 447 | RecordFieldCountMismatch(CountMismatch), |
|
| 448 | 448 | /// Record literal fields not in declaration order. |
|
| 449 | 449 | RecordFieldOutOfOrder { field: *[u8], prev: *[u8] }, |
|
| 450 | 450 | /// Function call supplied the wrong number of arguments. |
|
| 451 | 451 | FnArgCountMismatch(CountMismatch), |
|
| 452 | + | /// Function throws list has the wrong number of types. |
|
| 453 | + | FnThrowCountMismatch(CountMismatch), |
|
| 452 | 454 | /// Expected an identifier node. |
|
| 453 | 455 | ExpectedIdentifier, |
|
| 454 | 456 | /// Expected any optional type. |
|
| 455 | 457 | ExpectedOptional, |
|
| 456 | 458 | /// Expected a numeric type. |
| 3548 | 3550 | mutable: receiverMut, |
|
| 3549 | 3551 | }; |
|
| 3550 | 3552 | fnType.paramTypes[0] = allocType(self, receiverPtrType); |
|
| 3551 | 3553 | fnType.paramTypesLen = 1; |
|
| 3552 | 3554 | ||
| 3553 | - | // Add the other params from the trait method and validate that |
|
| 3554 | - | // the instance method's declared parameter types match. |
|
| 3555 | + | // Validate that the instance method's signature matches the |
|
| 3556 | + | // trait method's signature exactly (params, return type, throws). |
|
| 3555 | 3557 | if sig.params.len != tm.fnType.paramTypesLen { |
|
| 3556 | 3558 | throw emitError(self, methodNode, ErrorKind::FnArgCountMismatch(CountMismatch { |
|
| 3557 | 3559 | expected: tm.fnType.paramTypesLen, |
|
| 3558 | 3560 | actual: sig.params.len, |
|
| 3559 | 3561 | })); |
|
| 3560 | 3562 | } |
|
| 3561 | - | for j in 0..tm.fnType.paramTypesLen { |
|
| 3562 | - | // Resolve the instance method's declared parameter type. |
|
| 3563 | + | for j in 0..sig.params.len { |
|
| 3563 | 3564 | let paramNode = sig.params.list[j]; |
|
| 3564 | 3565 | let case ast::NodeValue::FnParam(param) = paramNode.value |
|
| 3565 | 3566 | else throw emitError(self, paramNode, ErrorKind::ExpectedIdentifier); |
|
| 3566 | 3567 | let instanceParamTy = try resolveValueType(self, param.type); |
|
| 3567 | - | let traitParamTy = *tm.fnType.paramTypes[j]; |
|
| 3568 | - | ||
| 3569 | - | if not typesEqual(instanceParamTy, traitParamTy) { |
|
| 3568 | + | if not typesEqual(instanceParamTy, *tm.fnType.paramTypes[j]) { |
|
| 3570 | 3569 | throw emitTypeMismatch(self, paramNode, TypeMismatch { |
|
| 3571 | - | expected: traitParamTy, |
|
| 3570 | + | expected: *tm.fnType.paramTypes[j], |
|
| 3572 | 3571 | actual: instanceParamTy, |
|
| 3573 | 3572 | }); |
|
| 3574 | 3573 | } |
|
| 3574 | + | } |
|
| 3575 | + | let mut instanceRetTy = Type::Void; |
|
| 3576 | + | if let retNode = sig.returnType { |
|
| 3577 | + | instanceRetTy = try resolveValueType(self, retNode); |
|
| 3578 | + | } |
|
| 3579 | + | if not typesEqual(instanceRetTy, *tm.fnType.returnType) { |
|
| 3580 | + | throw emitTypeMismatch(self, methodNode, TypeMismatch { |
|
| 3581 | + | expected: *tm.fnType.returnType, |
|
| 3582 | + | actual: instanceRetTy, |
|
| 3583 | + | }); |
|
| 3584 | + | } |
|
| 3585 | + | if sig.throwList.len != tm.fnType.throwListLen { |
|
| 3586 | + | throw emitError(self, methodNode, ErrorKind::FnThrowCountMismatch(CountMismatch { |
|
| 3587 | + | expected: tm.fnType.throwListLen, |
|
| 3588 | + | actual: sig.throwList.len, |
|
| 3589 | + | })); |
|
| 3590 | + | } |
|
| 3591 | + | for j in 0..sig.throwList.len { |
|
| 3592 | + | let instanceThrowTy = try resolveValueType(self, sig.throwList.list[j]); |
|
| 3593 | + | if not typesEqual(instanceThrowTy, *tm.fnType.throwList[j]) { |
|
| 3594 | + | throw emitTypeMismatch(self, sig.throwList.list[j], TypeMismatch { |
|
| 3595 | + | expected: *tm.fnType.throwList[j], |
|
| 3596 | + | actual: instanceThrowTy, |
|
| 3597 | + | }); |
|
| 3598 | + | } |
|
| 3599 | + | } |
|
| 3600 | + | ||
| 3601 | + | // Copy the trait's canonical types into the final function type. |
|
| 3602 | + | for j in 0..tm.fnType.paramTypesLen { |
|
| 3575 | 3603 | fnType.paramTypes[fnType.paramTypesLen] = tm.fnType.paramTypes[j]; |
|
| 3576 | 3604 | fnType.paramTypesLen += 1; |
|
| 3577 | 3605 | } |
|
| 3578 | 3606 | fnType.returnType = tm.fnType.returnType; |
|
| 3579 | - | ||
| 3580 | - | // Copy throws list from the trait method. |
|
| 3581 | 3607 | for j in 0..tm.fnType.throwListLen { |
|
| 3582 | 3608 | fnType.throwList[fnType.throwListLen] = tm.fnType.throwList[j]; |
|
| 3583 | 3609 | fnType.throwListLen += 1; |
|
| 3584 | 3610 | } |
|
| 3585 | 3611 |
lib/std/lang/resolver/printer.rad
+6 -0
| 353 | 353 | io::print("function argument count mismatch: expected "); |
|
| 354 | 354 | io::printU32(mismatch.expected); |
|
| 355 | 355 | io::print(", got "); |
|
| 356 | 356 | io::printU32(mismatch.actual); |
|
| 357 | 357 | } |
|
| 358 | + | case super::ErrorKind::FnThrowCountMismatch(mismatch) => { |
|
| 359 | + | io::print("function throws count mismatch: expected "); |
|
| 360 | + | io::printU32(mismatch.expected); |
|
| 361 | + | io::print(", got "); |
|
| 362 | + | io::printU32(mismatch.actual); |
|
| 363 | + | } |
|
| 358 | 364 | case super::ErrorKind::RecordFieldCountMismatch(mismatch) => { |
|
| 359 | 365 | io::print("record field count mismatch: expected "); |
|
| 360 | 366 | io::printU32(mismatch.expected); |
|
| 361 | 367 | io::print(", got "); |
|
| 362 | 368 | io::printU32(mismatch.actual); |
lib/std/lang/resolver/tests.rad
+50 -0
| 4681 | 4681 | let mut a = testResolver(); |
|
| 4682 | 4682 | let program = "trait Base { fn (*Base) f() -> i32; } trait Child: Base { fn (*Child) g() -> i32; } record R { x: i32 } instance Child for R { fn (r: *R) g() -> i32 { return r.x; } }"; |
|
| 4683 | 4683 | let result = try resolveProgramStr(&mut a, program); |
|
| 4684 | 4684 | try expectErrorKind(&result, super::ErrorKind::MissingSupertraitInstance("Base")); |
|
| 4685 | 4685 | } |
|
| 4686 | + | ||
| 4687 | + | /// Instance method omits return type when the trait declares `-> i32`. |
|
| 4688 | + | /// This is rejected -- the return type must be stated explicitly. |
|
| 4689 | + | @test fn testResolveInstanceReturnTypeOmitted() throws (testing::TestError) { |
|
| 4690 | + | let mut a = testResolver(); |
|
| 4691 | + | let program = "record R { x: i32 } trait T { fn (*T) get() -> i32; } instance T for R { fn (r: *R) get() { } }"; |
|
| 4692 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 4693 | + | let err = try expectError(&result); |
|
| 4694 | + | let case super::ErrorKind::TypeMismatch(_) = err.kind |
|
| 4695 | + | else throw testing::TestError::Failed; |
|
| 4696 | + | } |
|
| 4697 | + | ||
| 4698 | + | /// Instance method declares throws but the trait method does not throw. |
|
| 4699 | + | @test fn testResolveInstanceThrowsMismatchExtra() throws (testing::TestError) { |
|
| 4700 | + | let mut a = testResolver(); |
|
| 4701 | + | let program = "union E { Fail } record R { x: i32 } trait T { fn (*T) get() -> i32; } instance T for R { fn (r: *R) get() -> i32 throws (E) { return r.x; } }"; |
|
| 4702 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 4703 | + | let err = try expectError(&result); |
|
| 4704 | + | let case super::ErrorKind::FnThrowCountMismatch(_) = err.kind |
|
| 4705 | + | else throw testing::TestError::Failed; |
|
| 4706 | + | } |
|
| 4707 | + | ||
| 4708 | + | /// Instance method declares a different throws type than the trait. |
|
| 4709 | + | @test fn testResolveInstanceThrowsMismatchWrongType() throws (testing::TestError) { |
|
| 4710 | + | let mut a = testResolver(); |
|
| 4711 | + | let program = "union E1 { Fail } union E2 { Oops } record R { x: i32 } trait T { fn (*T) get() -> i32 throws (E1); } instance T for R { fn (r: *R) get() -> i32 throws (E2) { return r.x; } }"; |
|
| 4712 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 4713 | + | let err = try expectError(&result); |
|
| 4714 | + | let case super::ErrorKind::TypeMismatch(_) = err.kind |
|
| 4715 | + | else throw testing::TestError::Failed; |
|
| 4716 | + | } |
|
| 4717 | + | ||
| 4718 | + | /// Instance method omits throws clause when trait declares throws. |
|
| 4719 | + | /// This is rejected -- the throws clause must match exactly. |
|
| 4720 | + | @test fn testResolveInstanceThrowsOmitted() throws (testing::TestError) { |
|
| 4721 | + | let mut a = testResolver(); |
|
| 4722 | + | let program = "union E { Fail } record R { x: i32 } trait T { fn (*T) get() -> i32 throws (E); } instance T for R { fn (r: *R) get() -> i32 { throw E::Fail; return r.x; } }"; |
|
| 4723 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 4724 | + | let err = try expectError(&result); |
|
| 4725 | + | let case super::ErrorKind::FnThrowCountMismatch(_) = err.kind |
|
| 4726 | + | else throw testing::TestError::Failed; |
|
| 4727 | + | } |
|
| 4728 | + | ||
| 4729 | + | /// Instance method correctly matches the trait's throws clause. |
|
| 4730 | + | @test fn testResolveInstanceThrowsMatch() throws (testing::TestError) { |
|
| 4731 | + | let mut a = testResolver(); |
|
| 4732 | + | let program = "union E { Fail } record R { x: i32 } trait T { fn (*T) get() -> i32 throws (E); } instance T for R { fn (r: *R) get() -> i32 throws (E) { throw E::Fail; return r.x; } }"; |
|
| 4733 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 4734 | + | try expectNoErrors(&result); |
|
| 4735 | + | } |