diff --git a/explorer/ast/value.cpp b/explorer/ast/value.cpp index e1961fe2c42f..3c81f7087d65 100644 --- a/explorer/ast/value.cpp +++ b/explorer/ast/value.cpp @@ -585,13 +585,21 @@ void Value::Print(llvm::raw_ostream& out) const { case Value::Kind::FunctionType: { const auto& fn_type = cast(*this); out << "fn "; - if (!fn_type.deduced_bindings().empty()) { + auto self = fn_type.method_self(); + if (!fn_type.deduced_bindings().empty() || self.has_value()) { out << "["; llvm::ListSeparator sep; for (Nonnull deduced : fn_type.deduced_bindings()) { out << sep << *deduced; } + if (self.has_value()) { + if (self->addr_self) { + out << sep << "addr self: " << *self->self_type << "*"; + } else { + out << sep << "self: " << *self->self_type; + } + } out << "]"; } out << fn_type.parameters() << " -> " << fn_type.return_type(); @@ -844,6 +852,19 @@ auto TypeEqual(Nonnull t1, Nonnull t2, case Value::Kind::FunctionType: { const auto& fn1 = cast(*t1); const auto& fn2 = cast(*t2); + // Verify `self` parameters match + auto self1 = fn1.method_self(); + auto self2 = fn2.method_self(); + if (self1.has_value() != self2.has_value()) { + return false; + } + if (self1) { + if (self1->addr_self != self2->addr_self || + !TypeEqual(self1->self_type, self2->self_type, equality_ctx)) { + return false; + } + } + // Verify parameters and return types match return TypeEqual(&fn1.parameters(), &fn2.parameters(), equality_ctx) && TypeEqual(&fn1.return_type(), &fn2.return_type(), equality_ctx); } diff --git a/explorer/ast/value.h b/explorer/ast/value.h index 397eff2b7b28..010c26075ff5 100644 --- a/explorer/ast/value.h +++ b/explorer/ast/value.h @@ -662,18 +662,34 @@ class FunctionType : public Value { Nonnull binding; }; - FunctionType(Nonnull parameters, + // For methods with unbound `self` parameters. + struct MethodSelf { + template + auto Decompose(F f) const { + return f(addr_self, self_type); + } + + // True if `self` parameter uses an `addr` pattern. + bool addr_self; + // Type of `self` parameter. + const Value* self_type; + }; + + FunctionType(std::optional method_self, + Nonnull parameters, Nonnull return_type) - : FunctionType(parameters, {}, return_type, {}, {}, + : FunctionType(method_self, parameters, {}, return_type, {}, {}, /*is_initializing=*/false) {} - FunctionType(Nonnull parameters, + FunctionType(std::optional method_self, + Nonnull parameters, std::vector generic_parameters, Nonnull return_type, std::vector> deduced_bindings, std::vector> impl_bindings, bool is_initializing) : Value(Kind::FunctionType), + method_self_(method_self), parameters_(parameters), generic_parameters_(std::move(generic_parameters)), return_type_(return_type), @@ -681,14 +697,20 @@ class FunctionType : public Value { impl_bindings_(std::move(impl_bindings)), is_initializing_(is_initializing) {} + struct ExceptSelf {}; + FunctionType(ExceptSelf, const FunctionType& clone) + : FunctionType(std::nullopt, clone.parameters_, clone.generic_parameters_, + clone.return_type_, clone.deduced_bindings_, + clone.impl_bindings_, clone.is_initializing_) {} + static auto classof(const Value* value) -> bool { return value->kind() == Kind::FunctionType; } template auto Decompose(F f) const { - return f(parameters_, generic_parameters_, return_type_, deduced_bindings_, - impl_bindings_, is_initializing_); + return f(method_self_, parameters_, generic_parameters_, return_type_, + deduced_bindings_, impl_bindings_, is_initializing_); } // The type of the function parameter tuple. @@ -713,7 +735,11 @@ class FunctionType : public Value { // Return whether the function type is an initializing expression or not. auto is_initializing() const -> bool { return is_initializing_; } + // Binding for the implicit `self` parameter, if this is an unbound method. + auto method_self() const -> std::optional { return method_self_; } + private: + std::optional method_self_; Nonnull parameters_; std::vector generic_parameters_; Nonnull return_type_; diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 7988d66e82a3..ea2e95832927 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -2430,8 +2430,18 @@ class TypeChecker::SubstituteTransform CARBON_ASSIGN_OR_RETURN(const auto* ret, type_checker_->SubstituteImpl( subst_bindings.bindings(), &fn_type->return_type())); + std::optional method_self = + fn_type->method_self(); + if (method_self.has_value()) { + CARBON_ASSIGN_OR_RETURN( + const auto* self_type, + type_checker_->SubstituteImpl(subst_bindings.bindings(), + method_self->self_type)); + method_self->self_type = self_type; + } return type_checker_->arena_->New( - param, std::move(generic_parameters), ret, std::move(deduced_bindings), + method_self, param, std::move(generic_parameters), ret, + std::move(deduced_bindings), std::move(subst_bindings).TakeImplBindings(), fn_type->is_initializing()); } @@ -3082,10 +3092,10 @@ auto TypeChecker::TypeCheckExpImpl(Nonnull e, Nonnull field_type, Substitute(member_t_class->bindings(), member_type)); access.set_member(arena_->New(member)); - access.set_static_type(field_type); access.set_is_type_access(!IsInstanceMember(&access.member())); switch (member->kind()) { case DeclarationKind::VariableDeclaration: + access.set_static_type(field_type); access.set_expression_category( access.object().expression_category()); break; @@ -3093,6 +3103,14 @@ auto TypeChecker::TypeCheckExpImpl(Nonnull e, const auto* func_decl = cast(member); CARBON_RETURN_IF_ERROR(CheckAddrMeAccess( &access, func_decl, t_class.bindings(), impl_scope)); + if (access.is_type_access()) { + access.set_static_type(field_type); + } else { + // Remove `self` from type since now bound. + auto* function_type = cast(field_type); + access.set_static_type(arena_->New( + FunctionType::ExceptSelf{}, *function_type)); + } access.set_expression_category(ExpressionCategory::Value); break; } @@ -3162,13 +3180,22 @@ auto TypeChecker::TypeCheckExpImpl(Nonnull e, access.set_member(arena_->New(result.member)); access.set_found_in_interface(result.interface); access.set_is_type_access(!IsInstanceMember(&access.member())); - access.set_static_type(inst_member_type); access.set_expression_category(ExpressionCategory::Value); if (const auto* func_decl = dyn_cast(result.member)) { CARBON_RETURN_IF_ERROR( CheckAddrMeAccess(&access, func_decl, bindings, impl_scope)); + if (access.is_type_access()) { + access.set_static_type(inst_member_type); + } else { + // Remove `self` from type since now bound. + auto* function_type = cast(inst_member_type); + access.set_static_type(arena_->New( + FunctionType::ExceptSelf{}, *function_type)); + } + } else { + access.set_static_type(inst_member_type); } // TODO: This is just a ConstraintImplWitness into the @@ -3286,8 +3313,8 @@ auto TypeChecker::TypeCheckExpImpl(Nonnull e, Nonnull parameter_type, Substitute(choice.bindings(), *(*signature)->parameters_static_type())); - Nonnull type = - arena_->New(parameter_type, &choice); + Nonnull type = arena_->New( + std::nullopt, parameter_type, &choice); // TODO: Should there be a Declaration corresponding to each // choice type alternative? access.set_member( @@ -3465,6 +3492,16 @@ auto TypeChecker::TypeCheckExpImpl(Nonnull e, return Success(); }; + auto set_static_type_remove_self = [&]() -> ErrorOr { + Nonnull member_type = &member_name.member().type(); + CARBON_ASSIGN_OR_RETURN(member_type, + Substitute(bindings_for_member(), member_type)); + auto* function_type = cast(member_type); + access.set_static_type(arena_->New( + FunctionType::ExceptSelf{}, *function_type)); + return Success(); + }; + switch (std::optional> decl = member_name.member().declaration(); decl ? decl.value()->kind() @@ -3484,7 +3521,12 @@ auto TypeChecker::TypeCheckExpImpl(Nonnull e, CARBON_CHECK(!has_instance || is_instance_member || !member_name.base_type().has_value()) << "vacuous compound member access"; - CARBON_RETURN_IF_ERROR(set_static_type_as_member_type()); + // If this is instance access, remove self bound from function type + if (has_instance && is_instance_member) { + CARBON_RETURN_IF_ERROR(set_static_type_remove_self()); + } else { + CARBON_RETURN_IF_ERROR(set_static_type_as_member_type()); + } access.set_expression_category(ExpressionCategory::Value); CARBON_RETURN_IF_ERROR( CheckAddrMeAccess(&access, cast(*decl), @@ -3849,7 +3891,8 @@ auto TypeChecker::TypeCheckExpImpl(Nonnull e, TypeCheckTypeExp(&fn.return_type(), impl_scope)); fn.set_static_type(arena_->New()); fn.set_expression_category(ExpressionCategory::Value); - fn.set_constant_value(arena_->New(param, ret)); + fn.set_constant_value( + arena_->New(std::nullopt, param, ret)); return Success(); } case ExpressionKind::StringLiteral: @@ -5061,12 +5104,17 @@ auto TypeChecker::DeclareCallableDeclaration(Nonnull f, CollectImplBindingsInPattern(deduced, impl_bindings); } // Type check the receiver pattern. + std::optional method_self; if (f->is_method()) { CARBON_RETURN_IF_ERROR(TypeCheckPattern( &f->self_pattern(), PatternRequirements::Irrefutable, std::nullopt, function_scope, ExpressionCategory::Value)); CollectAndNumberGenericBindingsInPattern(&f->self_pattern(), all_bindings); CollectImplBindingsInPattern(&f->self_pattern(), impl_bindings); + FunctionType::MethodSelf method_self_present = { + (f->self_pattern().kind() == PatternKind::AddrPattern), + &f->self_pattern().static_type()}; + method_self = method_self_present; } // Type check the parameter pattern. CARBON_RETURN_IF_ERROR(TypeCheckPattern( @@ -5124,9 +5172,10 @@ auto TypeChecker::DeclareCallableDeclaration(Nonnull f, CARBON_CHECK(IsNonDeduceableType(&f->return_term().static_type())); f->set_static_type(arena_->New( - &f->param_pattern().static_type(), std::move(generic_parameters), - &f->return_term().static_type(), std::move(deduced_bindings), - std::move(impl_bindings), /*is_initializing*/ true)); + method_self, &f->param_pattern().static_type(), + std::move(generic_parameters), &f->return_term().static_type(), + std::move(deduced_bindings), std::move(impl_bindings), + /*is_initializing*/ true)); switch (f->kind()) { case DeclarationKind::FunctionDeclaration: // TODO: Should we pass in the bindings from the enclosing scope? diff --git a/explorer/interpreter/type_structure.cpp b/explorer/interpreter/type_structure.cpp index 9a819c32c191..d7848d9113ae 100644 --- a/explorer/interpreter/type_structure.cpp +++ b/explorer/interpreter/type_structure.cpp @@ -81,6 +81,7 @@ struct TypeStructureBuilder { void Visit(const Address&) {} void Visit(const VTable&) {} void Visit(const FunctionType::GenericParameter&) {} + void Visit(const FunctionType::MethodSelf&) {} void Visit(const NamedElement&) {} // Constraint types can contain mentions of VariableTypes, but they aren't diff --git a/explorer/testdata/class/bound_method_compound_member_access.carbon b/explorer/testdata/class/bound_method_compound_member_access.carbon new file mode 100644 index 000000000000..9a5e640e8e52 --- /dev/null +++ b/explorer/testdata/class/bound_method_compound_member_access.carbon @@ -0,0 +1,25 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class Point { + fn Origin() -> Point { + return {.x = 0, .y = 0}; + } + fn GetX[self: Point]() -> i32 { + return self.x; + } + var x: i32; + var y: i32; +} + +fn Main() -> i32 { + var p: Point = Point.Origin(); + var f: __Fn() -> i32 = p.(Point.GetX); + return f(); +} diff --git a/explorer/testdata/interface/fail_impl_adds_self.carbon b/explorer/testdata/interface/fail_impl_adds_self.carbon new file mode 100644 index 000000000000..a14d97768d1e --- /dev/null +++ b/explorer/testdata/interface/fail_impl_adds_self.carbon @@ -0,0 +1,24 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +package ExplorerTest api; + +interface I { + fn G(); +} + +class C { + impl as I { + // CHECK:STDERR: COMPILATION ERROR: fail_impl_adds_self.carbon:[[@LINE+3]]: type error in member of implementation + // CHECK:STDERR: expected: fn () -> () + // CHECK:STDERR: actual: fn [self: class C]() -> () + fn G[self: Self]() { } + } +} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/interface/fail_impl_bad_member.carbon b/explorer/testdata/interface/fail_impl_bad_member.carbon index 2d12557fa64f..ca4273c36ac6 100644 --- a/explorer/testdata/interface/fail_impl_bad_member.carbon +++ b/explorer/testdata/interface/fail_impl_bad_member.carbon @@ -21,8 +21,8 @@ class Point { fn Scale[self: Point](v: i32) -> i32 { return 0; // CHECK:STDERR: COMPILATION ERROR: fail_impl_bad_member.carbon:[[@LINE+3]]: type error in member of implementation - // CHECK:STDERR: expected: fn (i32,) -> class Point - // CHECK:STDERR: actual: fn (i32,) -> i32 + // CHECK:STDERR: expected: fn [self: class Point](i32,) -> class Point + // CHECK:STDERR: actual: fn [self: class Point](i32,) -> i32 } } } diff --git a/explorer/testdata/interface/fail_impl_mismatch_self.carbon b/explorer/testdata/interface/fail_impl_mismatch_self.carbon new file mode 100644 index 000000000000..e922c9a5609f --- /dev/null +++ b/explorer/testdata/interface/fail_impl_mismatch_self.carbon @@ -0,0 +1,24 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +package ExplorerTest api; + +interface I { + fn G[self: Self](); +} + +class C { + impl as I { + // CHECK:STDERR: COMPILATION ERROR: fail_impl_mismatch_self.carbon:[[@LINE+3]]: type error in member of implementation + // CHECK:STDERR: expected: fn [self: class C]() -> () + // CHECK:STDERR: actual: fn [addr self: class C*]() -> () + fn G[addr self: Self*]() { } + } +} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/interface/fail_impl_missing_self.carbon b/explorer/testdata/interface/fail_impl_missing_self.carbon new file mode 100644 index 000000000000..c96255cfaa2c --- /dev/null +++ b/explorer/testdata/interface/fail_impl_missing_self.carbon @@ -0,0 +1,24 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +package ExplorerTest api; + +interface I { + fn G[self: Self](); +} + +class C { + impl as I { + // CHECK:STDERR: COMPILATION ERROR: fail_impl_missing_self.carbon:[[@LINE+3]]: type error in member of implementation + // CHECK:STDERR: expected: fn [self: class C]() -> () + // CHECK:STDERR: actual: fn () -> () + fn G() { } + } +} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/member_access/fail_qualified_non_member.carbon b/explorer/testdata/member_access/fail_qualified_non_member.carbon index 3042b113de89..1f29b4a8300f 100644 --- a/explorer/testdata/member_access/fail_qualified_non_member.carbon +++ b/explorer/testdata/member_access/fail_qualified_non_member.carbon @@ -9,7 +9,7 @@ fn F[self: i32]() {} fn Main() -> i32 { // TODO: It's unclear whether this is valid per the current rules. See // https://github.com/carbon-language/carbon-lang/pull/1122 - // CHECK:STDERR: COMPILATION ERROR: fail_qualified_non_member.carbon:[[@LINE+1]]: expected name of instance member or interface member in compound member access, found fn () -> () + // CHECK:STDERR: COMPILATION ERROR: fail_qualified_non_member.carbon:[[@LINE+1]]: expected name of instance member or interface member in compound member access, found fn [self: i32]() -> () 42.(F)(); return 0; }