Improve type checking in trait instances

93e3225c9203988faa658014fb05e426a7368141aebf6bb9cdc4d11570fb784e
Method signatures were not properly checked.
Alexis Sellier committed ago 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 +
}