diff --git a/executable_semantics/ast/declaration.cpp b/executable_semantics/ast/declaration.cpp index a7bd9fcc849e..6d530c10cec0 100644 --- a/executable_semantics/ast/declaration.cpp +++ b/executable_semantics/ast/declaration.cpp @@ -80,7 +80,7 @@ void Declaration::PrintID(llvm::raw_ostream& out) const { switch (kind()) { case DeclarationKind::InterfaceDeclaration: { const auto& iface_decl = cast(*this); - out << "interface" << iface_decl.name(); + out << "interface " << iface_decl.name(); break; } case DeclarationKind::ImplDeclaration: { diff --git a/executable_semantics/ast/declaration.h b/executable_semantics/ast/declaration.h index 1907b2035b3e..8a3d5d573343 100644 --- a/executable_semantics/ast/declaration.h +++ b/executable_semantics/ast/declaration.h @@ -289,30 +289,37 @@ class InterfaceDeclaration : public Declaration { using ImplementsCarbonValueNode = void; InterfaceDeclaration(SourceLocation source_loc, std::string name, + std::optional> params, Nonnull self, std::vector> members) : Declaration(AstNodeKind::InterfaceDeclaration, source_loc), name_(std::move(name)), - members_(std::move(members)), - self_(self) {} + params_(std::move(params)), + self_(self), + members_(std::move(members)) {} static auto classof(const AstNode* node) -> bool { return InheritsFromInterfaceDeclaration(node->kind()); } auto name() const -> const std::string& { return name_; } + auto params() const -> std::optional> { + return params_; + } + auto params() -> std::optional> { return params_; } + auto self() const -> Nonnull { return self_; } + auto self() -> Nonnull { return self_; } auto members() const -> llvm::ArrayRef> { return members_; } - auto self() const -> Nonnull { return self_; } - auto self() -> Nonnull { return self_; } auto value_category() const -> ValueCategory { return ValueCategory::Let; } private: std::string name_; - std::vector> members_; + std::optional> params_; Nonnull self_; + std::vector> members_; }; enum class ImplKind { InternalImpl, ExternalImpl }; diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index 1562b7210cfd..c2c1e0ea4d22 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -633,20 +633,37 @@ auto Interpreter::CallFunction(const CallExpression& call, const ClassDeclaration& class_decl = class_type.declaration(); RuntimeScope type_params_scope(&heap_); BindingMap generic_args; - if (class_decl.type_params().has_value()) { - CHECK(PatternMatch(&(*class_decl.type_params())->value(), arg, - call.source_loc(), &type_params_scope, generic_args, - trace_stream_)); - switch (phase()) { - case Phase::RunTime: - return todo_.FinishAction(arena_->New( - &class_type.declaration(), generic_args, witnesses)); - case Phase::CompileTime: - return todo_.FinishAction(arena_->New( - &class_type.declaration(), generic_args, call.impls())); - } - } else { - FATAL() << "instantiation of non-generic class " << class_type; + CHECK(class_decl.type_params().has_value()) + << "instantiation of non-generic class " << class_type; + CHECK(PatternMatch(&(*class_decl.type_params())->value(), arg, + call.source_loc(), &type_params_scope, generic_args, + trace_stream_)); + switch (phase()) { + case Phase::RunTime: + return todo_.FinishAction(arena_->New( + &class_decl, generic_args, witnesses)); + case Phase::CompileTime: + return todo_.FinishAction(arena_->New( + &class_decl, generic_args, call.impls())); + } + } + case Value::Kind::InterfaceType: { + const InterfaceType& iface_type = cast(*fun); + const InterfaceDeclaration& iface_decl = iface_type.declaration(); + RuntimeScope params_scope(&heap_); + BindingMap generic_args; + CHECK(iface_decl.params().has_value()) + << "call of unparameterized interface " << iface_type; + CHECK(PatternMatch(&(*iface_decl.params())->value(), arg, + call.source_loc(), ¶ms_scope, generic_args, + trace_stream_)); + switch (phase()) { + case Phase::RunTime: + return todo_.FinishAction( + arena_->New(&iface_decl, generic_args, witnesses)); + case Phase::CompileTime: + return todo_.FinishAction(arena_->New( + &iface_decl, generic_args, call.impls())); } } default: diff --git a/executable_semantics/interpreter/resolve_names.cpp b/executable_semantics/interpreter/resolve_names.cpp index 77785e637086..93f6fa141368 100644 --- a/executable_semantics/interpreter/resolve_names.cpp +++ b/executable_semantics/interpreter/resolve_names.cpp @@ -303,6 +303,9 @@ static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope) auto& iface = cast(declaration); StaticScope iface_scope; iface_scope.AddParent(&enclosing_scope); + if (iface.params().has_value()) { + RETURN_IF_ERROR(ResolveNames(**iface.params(), iface_scope)); + } RETURN_IF_ERROR(iface_scope.Add("Self", iface.self())); for (Nonnull member : iface.members()) { RETURN_IF_ERROR(AddExposedNames(*member, iface_scope)); diff --git a/executable_semantics/interpreter/type_checker.cpp b/executable_semantics/interpreter/type_checker.cpp index 88baa3acb37d..959afd75713e 100644 --- a/executable_semantics/interpreter/type_checker.cpp +++ b/executable_semantics/interpreter/type_checker.cpp @@ -86,7 +86,6 @@ static auto IsType(Nonnull value) -> bool { case Value::Kind::FunctionType: case Value::Kind::PointerType: case Value::Kind::StructType: - case Value::Kind::InterfaceType: case Value::Kind::ChoiceType: case Value::Kind::ContinuationType: case Value::Kind::VariableType: @@ -105,14 +104,10 @@ static auto IsType(Nonnull value) -> bool { } return true; } - case Value::Kind::NominalClassType: { - const NominalClassType& class_type = cast(*value); - // A NominalClassType is concrete if - // 1) it is not a generic class (has no type parameters), or - // 2) it is a generic class applied to some type arguments. - return !class_type.declaration().type_params().has_value() || - !class_type.type_args().empty(); - } + case Value::Kind::NominalClassType: + return !cast(*value).IsParameterized(); + case Value::Kind::InterfaceType: + return !cast(*value).IsParameterized(); } } @@ -152,7 +147,6 @@ static auto IsConcreteType(Nonnull value) -> bool { case Value::Kind::FunctionType: case Value::Kind::PointerType: case Value::Kind::StructType: - case Value::Kind::InterfaceType: case Value::Kind::ChoiceType: case Value::Kind::ContinuationType: case Value::Kind::VariableType: @@ -173,14 +167,10 @@ static auto IsConcreteType(Nonnull value) -> bool { } return true; } - case Value::Kind::NominalClassType: { - const NominalClassType& class_type = cast(*value); - // A NominalClassType is concrete if - // 1) it is not a generic class (has no type parameters), or - // 2) it is a generic class applied to some type arguments. - return !class_type.declaration().type_params().has_value() || - !class_type.type_args().empty(); - } + case Value::Kind::NominalClassType: + return !cast(*value).IsParameterized(); + case Value::Kind::InterfaceType: + return !cast(*value).IsParameterized(); } } @@ -303,9 +293,7 @@ auto TypeChecker::IsImplicitlyConvertible( case Value::Kind::InterfaceType: return destination->kind() == Value::Kind::TypeType; case Value::Kind::TypeOfClassType: { - const auto& class_type = cast(*source).class_type(); - return ((!class_type.declaration().type_params().has_value()) || - (!class_type.type_args().empty())) && + return !cast(*source).class_type().IsParameterized() && destination->kind() == Value::Kind::TypeType; } default: @@ -538,12 +526,25 @@ auto TypeChecker::Substitute( } return new_class_type; } + case Value::Kind::InterfaceType: { + const auto& iface_type = cast(*type); + BindingMap args; + for (const auto& [name, value] : iface_type.args()) { + args[name] = Substitute(dict, value); + } + Nonnull new_iface_type = + arena_->New(&iface_type.declaration(), args); + if (trace_stream_) { + **trace_stream_ << "substitution: " << iface_type << " => " + << *new_iface_type << "\n"; + } + return new_iface_type; + } case Value::Kind::StaticArrayType: case Value::Kind::AutoType: case Value::Kind::IntType: case Value::Kind::BoolType: case Value::Kind::TypeType: - case Value::Kind::InterfaceType: case Value::Kind::ChoiceType: case Value::Kind::ContinuationType: case Value::Kind::StringType: @@ -634,11 +635,12 @@ auto TypeChecker::SatisfyImpls( BindingMap& deduced_type_args, ImplExpMap& impls) const -> ErrorOr { for (Nonnull impl_binding : impl_bindings) { - ASSIGN_OR_RETURN( - Nonnull impl, - impl_scope.Resolve(impl_binding->interface(), - deduced_type_args[impl_binding->type_var()], - source_loc, *this)); + Nonnull interface = + Substitute(deduced_type_args, impl_binding->interface()); + ASSIGN_OR_RETURN(Nonnull impl, + impl_scope.Resolve( + interface, deduced_type_args[impl_binding->type_var()], + source_loc, *this)); impls.emplace(impl_binding, impl); } return Success(); @@ -839,11 +841,10 @@ auto TypeChecker::TypeCheckExp(Nonnull e, FindMember(access.field(), iface_decl.members()); member.has_value()) { const Value& member_type = (*member)->static_type(); - std::map, Nonnull> - self_map; - self_map[iface_decl.self()] = &var_type; + BindingMap binding_map = iface_type.args(); + binding_map[iface_decl.self()] = &var_type; Nonnull inst_member_type = - Substitute(self_map, &member_type); + Substitute(binding_map, &member_type); access.set_static_type(inst_member_type); CHECK(var_type.binding().impl_binding().has_value()); access.set_impl(*var_type.binding().impl_binding()); @@ -877,8 +878,10 @@ auto TypeChecker::TypeCheckExp(Nonnull e, FindMember(access.field(), iface_decl.members()); member.has_value()) { const Value& member_type = (*member)->static_type(); + BindingMap binding_map = iface_type.args(); + binding_map[iface_decl.self()] = &var_type; Nonnull inst_member_type = - Substitute({{iface_decl.self(), &var_type}}, &member_type); + Substitute(binding_map, &member_type); access.set_static_type(inst_member_type); CHECK(var_type.binding().impl_binding().has_value()); access.set_impl(*var_type.binding().impl_binding()); @@ -1060,8 +1063,7 @@ auto TypeChecker::TypeCheckExp(Nonnull e, cast(call.function().static_type()).class_type(); const ClassDeclaration& class_decl = class_type.declaration(); BindingMap generic_args; - if (class_decl.type_params().has_value() && - class_type.type_args().empty()) { + if (class_type.IsParameterized()) { if (trace_stream_) { **trace_stream_ << "pattern matching type params and args\n"; } @@ -1089,23 +1091,11 @@ auto TypeChecker::TypeCheckExp(Nonnull e, if (binding->impl_binding().has_value()) { Nonnull impl_binding = *binding->impl_binding(); - switch (impl_binding->interface()->kind()) { - case Value::Kind::InterfaceType: { - ASSIGN_OR_RETURN( - Nonnull impl, - impl_scope.Resolve(impl_binding->interface(), - generic_args[binding], - call.source_loc(), *this)); - impls.emplace(impl_binding, impl); - break; - } - case Value::Kind::TypeType: - break; - default: - return CompilationError(e->source_loc()) - << "unexpected type of deduced parameter " - << *impl_binding->interface(); - } + ASSIGN_OR_RETURN(Nonnull impl, + impl_scope.Resolve(impl_binding->interface(), + generic_args[binding], + call.source_loc(), *this)); + impls.emplace(impl_binding, impl); } } Nonnull inst_class_type = @@ -1115,6 +1105,59 @@ auto TypeChecker::TypeCheckExp(Nonnull e, call.set_value_category(ValueCategory::Let); return Success(); } + case Value::Kind::TypeOfInterfaceType: { + // This case handles the application of a parameterized class to + // its arguments, such as OrderedWith(i32). + // FIXME: This is very repetitive with the corresponding case for + // parameterized classes. + const InterfaceType& iface_type = + cast(call.function().static_type()) + .interface_type(); + const InterfaceDeclaration& iface_decl = iface_type.declaration(); + BindingMap generic_args; + if (iface_type.IsParameterized()) { + if (trace_stream_) { + **trace_stream_ << "pattern matching interface params and args\n"; + } + RETURN_IF_ERROR(ExpectType(call.source_loc(), "call", + &(*iface_decl.params())->static_type(), + &call.argument().static_type())); + ASSIGN_OR_RETURN( + Nonnull arg, + InterpExp(&call.argument(), arena_, trace_stream_)); + CHECK(PatternMatch(&(*iface_decl.params())->value(), arg, + call.source_loc(), std::nullopt, generic_args, + trace_stream_)); + } else if (!iface_type.args().empty()) { + return CompilationError(call.source_loc()) + << "attempt to provide arguments to parameterized interface " + << "twice: " << *e; + } else { + return CompilationError(call.source_loc()) + << "attempt to provide arguments to non-parameterized " + << "interface: " << *e; + } + // Find impls for all the impl bindings of the interface. + ImplExpMap impls; + for (const auto& [binding, val] : generic_args) { + if (binding->impl_binding().has_value()) { + Nonnull impl_binding = + *binding->impl_binding(); + ASSIGN_OR_RETURN(Nonnull impl, + impl_scope.Resolve(impl_binding->interface(), + generic_args[binding], + call.source_loc(), *this)); + impls.emplace(impl_binding, impl); + } + } + Nonnull inst_iface_type = + arena_->New(&iface_decl, generic_args, impls); + call.set_impls(impls); + call.set_static_type( + arena_->New(inst_iface_type)); + call.set_value_category(ValueCategory::Let); + return Success(); + } default: { return CompilationError(e->source_loc()) << "in call, expected a function\n" @@ -1218,33 +1261,32 @@ auto TypeChecker::TypeCheckExp(Nonnull e, } } -void TypeChecker::AddPatternImpls(Nonnull p, ImplScope& impl_scope) { +void TypeChecker::CollectImplBindingsInPattern( + Nonnull p, + std::vector>& impl_bindings) { switch (p->kind()) { case PatternKind::GenericBinding: { auto& binding = cast(*p); - CHECK(binding.impl_binding().has_value()); - Nonnull impl_binding = *binding.impl_binding(); - auto impl_id = arena_->New(p->source_loc(), "impl"); - impl_id->set_value_node(impl_binding); - impl_scope.Add(impl_binding->interface(), - *impl_binding->type_var()->symbolic_identity(), impl_id); + if (binding.impl_binding().has_value()) { + impl_bindings.push_back(*binding.impl_binding()); + } return; } case PatternKind::TuplePattern: { auto& tuple = cast(*p); - for (Nonnull field : tuple.fields()) { - AddPatternImpls(field, impl_scope); + for (Nonnull field : tuple.fields()) { + CollectImplBindingsInPattern(field, impl_bindings); } return; } case PatternKind::AlternativePattern: { auto& alternative = cast(*p); - AddPatternImpls(&alternative.arguments(), impl_scope); + CollectImplBindingsInPattern(&alternative.arguments(), impl_bindings); return; } case PatternKind::VarPattern: { auto& var_pattern = cast(*p); - AddPatternImpls(&var_pattern.pattern(), impl_scope); + CollectImplBindingsInPattern(&var_pattern.pattern(), impl_bindings); return; } case PatternKind::ExpressionPattern: @@ -1254,9 +1296,34 @@ void TypeChecker::AddPatternImpls(Nonnull p, ImplScope& impl_scope) { } } +void TypeChecker::BringPatternImplsIntoScope(Nonnull p, + ImplScope& impl_scope) { + std::vector> impl_bindings; + CollectImplBindingsInPattern(p, impl_bindings); + BringImplsIntoScope(impl_bindings, impl_scope); +} + +void TypeChecker::BringImplsIntoScope( + llvm::ArrayRef> impl_bindings, + ImplScope& impl_scope) { + for (Nonnull impl_binding : impl_bindings) { + BringImplIntoScope(impl_binding, impl_scope); + } +} + +void TypeChecker::BringImplIntoScope(Nonnull impl_binding, + ImplScope& impl_scope) { + CHECK(impl_binding->type_var()->symbolic_identity().has_value()); + auto impl_id = + arena_->New(impl_binding->source_loc(), "impl"); + impl_id->set_value_node(impl_binding); + impl_scope.Add(impl_binding->interface(), + *impl_binding->type_var()->symbolic_identity(), impl_id); +} + auto TypeChecker::TypeCheckPattern( Nonnull p, std::optional> expected, - const ImplScope& impl_scope, ValueCategory enclosing_value_category) + ImplScope& impl_scope, ValueCategory enclosing_value_category) -> ErrorOr { if (trace_stream_) { **trace_stream_ << "checking pattern " << *p; @@ -1324,10 +1391,14 @@ auto TypeChecker::TypeCheckPattern( ASSIGN_OR_RETURN(Nonnull val, InterpPattern(&binding, arena_, trace_stream_)); binding.set_symbolic_identity(val); - Nonnull impl_binding = arena_->New( - binding.source_loc(), &binding, &binding.static_type()); - binding.set_impl_binding(impl_binding); SetValue(&binding, val); + + if (isa(type)) { + Nonnull impl_binding = + arena_->New(binding.source_loc(), &binding, type); + binding.set_impl_binding(impl_binding); + BringImplIntoScope(impl_binding, impl_scope); + } return Success(); } case PatternKind::TuplePattern: { @@ -1428,10 +1499,12 @@ auto TypeChecker::TypeCheckStmt(Nonnull s, RETURN_IF_ERROR(TypeCheckExp(&match.expression(), impl_scope)); std::vector new_clauses; for (auto& clause : match.clauses()) { + ImplScope clause_scope; + clause_scope.AddParent(&impl_scope); RETURN_IF_ERROR(TypeCheckPattern(&clause.pattern(), &match.expression().static_type(), - impl_scope, ValueCategory::Let)); - RETURN_IF_ERROR(TypeCheckStmt(&clause.statement(), impl_scope)); + clause_scope, ValueCategory::Let)); + RETURN_IF_ERROR(TypeCheckStmt(&clause.statement(), clause_scope)); } return Success(); } @@ -1458,7 +1531,16 @@ auto TypeChecker::TypeCheckStmt(Nonnull s, auto& var = cast(*s); RETURN_IF_ERROR(TypeCheckExp(&var.init(), impl_scope)); const Value& rhs_ty = var.init().static_type(); - RETURN_IF_ERROR(TypeCheckPattern(&var.pattern(), &rhs_ty, impl_scope, + // FIXME: If the pattern contains a binding that implies a new impl is + // available, should that remain in scope for as long as its binding? + // ``` + // var a: (T:! Widget) = ...; + // // Is the `impl T as Widget` in scope here? + // a.(Widget.F)(); + // ``` + ImplScope var_scope; + var_scope.AddParent(&impl_scope); + RETURN_IF_ERROR(TypeCheckPattern(&var.pattern(), &rhs_ty, var_scope, var.value_category())); return Success(); } @@ -1604,45 +1686,6 @@ auto TypeChecker::ExpectReturnOnAllPaths( } } -auto TypeChecker::CreateImplBindings( - llvm::ArrayRef> deduced_parameters, - SourceLocation source_loc, - std::vector>& impl_bindings) - -> ErrorOr { - for (Nonnull deduced : deduced_parameters) { - switch (deduced->static_type().kind()) { - case Value::Kind::InterfaceType: { - Nonnull impl_binding = arena_->New( - deduced->source_loc(), deduced, &deduced->static_type()); - deduced->set_impl_binding(impl_binding); - impl_binding->set_static_type(&deduced->static_type()); - impl_bindings.push_back(impl_binding); - break; - } - case Value::Kind::TypeType: - // No `impl` binding needed for type parameter with bound `Type`. - break; - default: - return CompilationError(source_loc) - << "unexpected type of deduced parameter " - << deduced->static_type(); - } - } - return Success(); -} - -void TypeChecker::BringImplsIntoScope( - llvm::ArrayRef> impl_bindings, ImplScope& scope, - SourceLocation source_loc) { - for (Nonnull impl_binding : impl_bindings) { - CHECK(impl_binding->type_var()->symbolic_identity().has_value()); - auto impl_id = arena_->New(source_loc, "impl"); - impl_id->set_value_node(impl_binding); - scope.Add(impl_binding->interface(), - *impl_binding->type_var()->symbolic_identity(), impl_id); - } -} - // TODO: Add checking to function definitions to ensure that // all deduced type parameters will be deduced. auto TypeChecker::DeclareFunctionDeclaration(Nonnull f, @@ -1651,30 +1694,25 @@ auto TypeChecker::DeclareFunctionDeclaration(Nonnull f, if (trace_stream_) { **trace_stream_ << "** declaring function " << f->name() << "\n"; } - // Bring the deduced parameters into scope. - for (Nonnull deduced : f->deduced_parameters()) { - RETURN_IF_ERROR(TypeCheckExp(&deduced->type(), enclosing_scope)); - deduced->set_symbolic_identity(arena_->New(deduced)); - ASSIGN_OR_RETURN(Nonnull type_of_type, - InterpExp(&deduced->type(), arena_, trace_stream_)); - deduced->set_static_type(type_of_type); - } - // Create the impl_bindings. - std::vector> impl_bindings; - RETURN_IF_ERROR(CreateImplBindings(f->deduced_parameters(), f->source_loc(), - impl_bindings)); - // Bring the impl bindings into scope. ImplScope function_scope; function_scope.AddParent(&enclosing_scope); - BringImplsIntoScope(impl_bindings, function_scope, f->source_loc()); + std::vector> impl_bindings; + // Bring the deduced parameters into scope. + for (Nonnull deduced : f->deduced_parameters()) { + RETURN_IF_ERROR(TypeCheckPattern(deduced, std::nullopt, function_scope, + ValueCategory::Let)); + CollectImplBindingsInPattern(deduced, impl_bindings); + } // Type check the receiver pattern. if (f->is_method()) { RETURN_IF_ERROR(TypeCheckPattern(&f->me_pattern(), std::nullopt, function_scope, ValueCategory::Let)); + CollectImplBindingsInPattern(&f->me_pattern(), impl_bindings); } // Type check the parameter pattern. RETURN_IF_ERROR(TypeCheckPattern(&f->param_pattern(), std::nullopt, function_scope, ValueCategory::Let)); + CollectImplBindingsInPattern(&f->param_pattern(), impl_bindings); // Evaluate the return type, if we can do so without examining the body. if (std::optional> return_expression = @@ -1737,11 +1775,11 @@ auto TypeChecker::TypeCheckFunctionDeclaration(Nonnull f, // if f->return_term().is_auto(), the function body was already // type checked in DeclareFunctionDeclaration. if (f->body().has_value() && !f->return_term().is_auto()) { - // Bring the impl's into scope. + // Bring the impls into scope. ImplScope function_scope; function_scope.AddParent(&impl_scope); BringImplsIntoScope(cast(f->static_type()).impl_bindings(), - function_scope, f->source_loc()); + function_scope); if (trace_stream_) **trace_stream_ << function_scope; RETURN_IF_ERROR(TypeCheckStmt(*f->body(), function_scope)); @@ -1766,7 +1804,6 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull class_decl, class_scope.AddParent(&enclosing_scope); RETURN_IF_ERROR(TypeCheckPattern(*class_decl->type_params(), std::nullopt, class_scope, ValueCategory::Let)); - AddPatternImpls(*class_decl->type_params(), class_scope); if (trace_stream_) { **trace_stream_ << class_scope; } @@ -1779,8 +1816,6 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull class_decl, for (Nonnull m : class_decl->members()) { RETURN_IF_ERROR(DeclareDeclaration(m, class_scope)); } - - // TODO: when/how to bring impls in generic class into scope? } else { // The declarations of the members may refer to the class, so we // must set the constant value of the class and its static type @@ -1810,7 +1845,7 @@ auto TypeChecker::TypeCheckClassDeclaration( ImplScope class_scope; class_scope.AddParent(&impl_scope); if (class_decl->type_params().has_value()) { - AddPatternImpls(*class_decl->type_params(), class_scope); + BringPatternImplsIntoScope(*class_decl->type_params(), class_scope); } if (trace_stream_) { **trace_stream_ << class_scope; @@ -1828,15 +1863,33 @@ auto TypeChecker::TypeCheckClassDeclaration( auto TypeChecker::DeclareInterfaceDeclaration( Nonnull iface_decl, ImplScope& enclosing_scope) -> ErrorOr { + if (trace_stream_) { + **trace_stream_ << "** declaring interface " << iface_decl->name() << "\n"; + } + ImplScope iface_scope; + iface_scope.AddParent(&enclosing_scope); + + if (iface_decl->params().has_value()) { + RETURN_IF_ERROR(TypeCheckPattern(*iface_decl->params(), std::nullopt, + iface_scope, ValueCategory::Let)); + if (trace_stream_) { + **trace_stream_ << iface_scope; + } + } + Nonnull iface_type = arena_->New(iface_decl); SetConstantValue(iface_decl, iface_type); iface_decl->set_static_type(arena_->New(iface_type)); // Process the Self parameter. RETURN_IF_ERROR(TypeCheckPattern(iface_decl->self(), std::nullopt, - enclosing_scope, ValueCategory::Let)); + iface_scope, ValueCategory::Let)); for (Nonnull m : iface_decl->members()) { - RETURN_IF_ERROR(DeclareDeclaration(m, enclosing_scope)); + RETURN_IF_ERROR(DeclareDeclaration(m, iface_scope)); + } + if (trace_stream_) { + **trace_stream_ << "** finished declaring interface " << iface_decl->name() + << "\n"; } return Success(); } @@ -1844,8 +1897,23 @@ auto TypeChecker::DeclareInterfaceDeclaration( auto TypeChecker::TypeCheckInterfaceDeclaration( Nonnull iface_decl, const ImplScope& impl_scope) -> ErrorOr { + if (trace_stream_) { + **trace_stream_ << "** checking interface " << iface_decl->name() << "\n"; + } + ImplScope iface_scope; + iface_scope.AddParent(&impl_scope); + if (iface_decl->params().has_value()) { + BringPatternImplsIntoScope(*iface_decl->params(), iface_scope); + } + if (trace_stream_) { + **trace_stream_ << iface_scope; + } for (Nonnull m : iface_decl->members()) { - RETURN_IF_ERROR(TypeCheckDeclaration(m, impl_scope)); + RETURN_IF_ERROR(TypeCheckDeclaration(m, iface_scope)); + } + if (trace_stream_) { + **trace_stream_ << "** finished checking interface " << iface_decl->name() + << "\n"; } return Success(); } @@ -1857,29 +1925,35 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull impl_decl, **trace_stream_ << "declaring " << *impl_decl << "\n"; } RETURN_IF_ERROR(TypeCheckExp(&impl_decl->interface(), enclosing_scope)); - ASSIGN_OR_RETURN(Nonnull iface_type, + ASSIGN_OR_RETURN(Nonnull written_iface_type, InterpExp(&impl_decl->interface(), arena_, trace_stream_)); - const auto& iface_decl = cast(*iface_type).declaration(); + + const auto* iface_type = dyn_cast(written_iface_type); + if (!iface_type) { + return CompilationError(impl_decl->interface().source_loc()) + << "expected constraint after `as`, found value of type " + << *written_iface_type; + } + if (iface_type->IsParameterized()) { + return CompilationError(impl_decl->interface().source_loc()) + << "missing arguments for parameterized interface"; + } + + const auto& iface_decl = iface_type->declaration(); impl_decl->set_interface_type(iface_type); + ImplScope impl_scope; + impl_scope.AddParent(&enclosing_scope); + std::vector> impl_bindings; + // Bring the deduced parameters into scope. for (Nonnull deduced : impl_decl->deduced_parameters()) { - RETURN_IF_ERROR(TypeCheckExp(&deduced->type(), enclosing_scope)); - deduced->set_symbolic_identity(arena_->New(deduced)); - ASSIGN_OR_RETURN(Nonnull type_of_type, - InterpExp(&deduced->type(), arena_, trace_stream_)); - deduced->set_static_type(type_of_type); + RETURN_IF_ERROR(TypeCheckPattern(deduced, std::nullopt, impl_scope, + ValueCategory::Let)); + CollectImplBindingsInPattern(deduced, impl_bindings); } - // Create the impl_bindings. - std::vector> impl_bindings; - RETURN_IF_ERROR(CreateImplBindings(impl_decl->deduced_parameters(), - impl_decl->source_loc(), impl_bindings)); impl_decl->set_impl_bindings(impl_bindings); - // Bring the impl bindings into scope for the impl body. - ImplScope impl_scope; - impl_scope.AddParent(&enclosing_scope); - BringImplsIntoScope(impl_bindings, impl_scope, impl_decl->source_loc()); // Check and interpret the impl_type RETURN_IF_ERROR(TypeCheckExp(impl_decl->impl_type(), impl_scope)); ASSIGN_OR_RETURN(Nonnull impl_type_value, @@ -1902,11 +1976,10 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull impl_decl, if (std::optional> mem = FindMember(*mem_name, impl_decl->members()); mem.has_value()) { - std::map, Nonnull> - self_map; - self_map[iface_decl.self()] = impl_type_value; + BindingMap binding_map = iface_type->args(); + binding_map[iface_decl.self()] = impl_type_value; Nonnull iface_mem_type = - Substitute(self_map, &m->static_type()); + Substitute(binding_map, &m->static_type()); RETURN_IF_ERROR(ExpectType((*mem)->source_loc(), "member of implementation", iface_mem_type, &(*mem)->static_type())); @@ -1930,11 +2003,10 @@ auto TypeChecker::TypeCheckImplDeclaration(Nonnull impl_decl, if (trace_stream_) { **trace_stream_ << "checking " << *impl_decl << "\n"; } - // Bring the impl's from the parameters into scope. + // Bring the impls from the parameters into scope. ImplScope impl_scope; impl_scope.AddParent(&enclosing_scope); - BringImplsIntoScope(impl_decl->impl_bindings(), impl_scope, - impl_decl->source_loc()); + BringImplsIntoScope(impl_decl->impl_bindings(), impl_scope); for (Nonnull m : impl_decl->members()) { RETURN_IF_ERROR(TypeCheckDeclaration(m, impl_scope)); } diff --git a/executable_semantics/interpreter/type_checker.h b/executable_semantics/interpreter/type_checker.h index 115d0778d5cb..d989072281f1 100644 --- a/executable_semantics/interpreter/type_checker.h +++ b/executable_semantics/interpreter/type_checker.h @@ -64,9 +64,11 @@ class TypeChecker { // `expected` is the type that this pattern is expected to have, if the // surrounding context gives us that information. Otherwise, it is // nullopt. + // + // `impl_scope` is extended with all impls implied by the pattern. auto TypeCheckPattern(Nonnull p, std::optional> expected, - const ImplScope& impl_scope, + ImplScope& impl_scope, ValueCategory enclosing_value_category) -> ErrorOr; @@ -104,8 +106,24 @@ class TypeChecker { const ImplScope& enclosing_scope) -> ErrorOr; + // Find all of the ImplBindings in the given pattern. The pattern is required + // to have already been type-checked. + void CollectImplBindingsInPattern( + Nonnull p, + std::vector>& impl_bindings); + // Add the impls from the pattern into the given `impl_scope`. - void AddPatternImpls(Nonnull p, ImplScope& impl_scope); + void BringPatternImplsIntoScope(Nonnull p, + ImplScope& impl_scope); + + // Add the given ImplBinding to the given `impl_scope`. + void BringImplIntoScope(Nonnull impl_binding, + ImplScope& impl_scope); + + // Add all of the `impl_bindings` into the `scope`. + void BringImplsIntoScope( + llvm::ArrayRef> impl_bindings, + ImplScope& scope); // Checks the statements and (runtime) expressions within the // declaration, such as the body of a function. @@ -184,21 +202,6 @@ class TypeChecker { Nonnull>& dict, Nonnull type) const -> Nonnull; - // For each deduced type parameter of a generic that has a - // non-trivial type (such as an interface), create an impl binding - // to serve as the parameter for passing a witness at runtime for - // the required impl. - auto CreateImplBindings( - llvm::ArrayRef> deduced_parameters, - SourceLocation source_loc, - std::vector>& impl_bindings) - -> ErrorOr; - - // Add all of the `impl_bindings` into the `scope`. - void BringImplsIntoScope( - llvm::ArrayRef> impl_bindings, - ImplScope& scope, SourceLocation source_loc); - // Find impls that satisfy all of the `impl_bindings`, but with the // type variables in the `impl_bindings` replaced by the argument // type in `deduced_type_args`. The results are placed in the diff --git a/executable_semantics/interpreter/value.cpp b/executable_semantics/interpreter/value.cpp index f1fb99e5ff3c..7393e12987a8 100644 --- a/executable_semantics/interpreter/value.cpp +++ b/executable_semantics/interpreter/value.cpp @@ -334,6 +334,14 @@ void Value::Print(llvm::raw_ostream& out) const { case Value::Kind::InterfaceType: { const auto& iface_type = cast(*this); out << "interface " << iface_type.declaration().name(); + if (!iface_type.args().empty()) { + out << "("; + llvm::ListSeparator sep; + for (const auto& [bind, val] : iface_type.args()) { + out << sep << bind->name() << " = " << *val; + } + out << ")"; + } break; } case Value::Kind::Witness: { @@ -456,15 +464,23 @@ auto TypeEqual(Nonnull t1, Nonnull t2) -> bool { } for (const auto& [ty_var1, ty1] : cast(*t1).type_args()) { - if (!TypeEqual(ty1, - cast(*t2).type_args().at(ty_var1))) { + if (!ValueEqual(ty1, + cast(*t2).type_args().at(ty_var1))) { return false; } } return true; case Value::Kind::InterfaceType: - return cast(*t1).declaration().name() == - cast(*t2).declaration().name(); + if (cast(*t1).declaration().name() != + cast(*t2).declaration().name()) { + return false; + } + for (const auto& [ty_var1, ty1] : cast(*t1).args()) { + if (!ValueEqual(ty1, cast(*t2).args().at(ty_var1))) { + return false; + } + } + return true; case Value::Kind::ChoiceType: return cast(*t1).name() == cast(*t2).name(); case Value::Kind::TupleValue: { diff --git a/executable_semantics/interpreter/value.h b/executable_semantics/interpreter/value.h index bdc70849ac86..b37baee7d016 100644 --- a/executable_semantics/interpreter/value.h +++ b/executable_semantics/interpreter/value.h @@ -560,6 +560,12 @@ class NominalClassType : public Value { // instantiated runtime type of a generic class. auto witnesses() const -> const ImplWitnessMap& { return witnesses_; } + // Returns whether this a parameterized class. That is, a class with + // parameters and no corresponding arguments. + auto IsParameterized() const -> bool { + return declaration_->type_params().has_value() && type_args_.empty(); + } + // Returns the value of the function named `name` in this class, or // nullopt if there is no such function. auto FindFunction(const std::string& name) const @@ -582,6 +588,21 @@ class InterfaceType : public Value { public: explicit InterfaceType(Nonnull declaration) : Value(Kind::InterfaceType), declaration_(declaration) {} + explicit InterfaceType(Nonnull declaration, + const BindingMap& args) + : Value(Kind::InterfaceType), declaration_(declaration), args_(args) {} + explicit InterfaceType(Nonnull declaration, + const BindingMap& args, const ImplExpMap& impls) + : Value(Kind::InterfaceType), + declaration_(declaration), + args_(args), + impls_(impls) {} + explicit InterfaceType(Nonnull declaration, + const BindingMap& args, const ImplWitnessMap& wits) + : Value(Kind::InterfaceType), + declaration_(declaration), + args_(args), + witnesses_(wits) {} static auto classof(const Value* value) -> bool { return value->kind() == Kind::InterfaceType; @@ -590,9 +611,21 @@ class InterfaceType : public Value { auto declaration() const -> const InterfaceDeclaration& { return *declaration_; } + auto args() const -> const BindingMap& { return args_; } + + // FIXME: These aren't used for anything yet. + auto impls() const -> const ImplExpMap& { return impls_; } + auto witnesses() const -> const ImplWitnessMap& { return witnesses_; } + + auto IsParameterized() const -> bool { + return declaration_->params().has_value() && args_.empty(); + } private: Nonnull declaration_; + BindingMap args_; + ImplExpMap impls_; + ImplWitnessMap witnesses_; }; // The witness table for an impl. diff --git a/executable_semantics/syntax/parser.ypp b/executable_semantics/syntax/parser.ypp index f88ca389dcf4..8c1cc0662b94 100644 --- a/executable_semantics/syntax/parser.ypp +++ b/executable_semantics/syntax/parser.ypp @@ -909,12 +909,13 @@ declaration: $$ = arena->New(context.source_loc(), $2, $4, ValueCategory::Let); } -| INTERFACE identifier LEFT_CURLY_BRACE declaration_list RIGHT_CURLY_BRACE +| INTERFACE identifier type_params LEFT_CURLY_BRACE declaration_list RIGHT_CURLY_BRACE { auto ty_ty = arena -> New(context.source_loc()); auto self = arena -> New(context.source_loc(), "Self", ty_ty); - $$ = arena->New(context.source_loc(), $2, self, $4); + $$ = arena->New(context.source_loc(), $2, $3, self, + $5); } | impl_kind IMPL impl_deduced_params expression AS expression LEFT_CURLY_BRACE declaration_list RIGHT_CURLY_BRACE { diff --git a/executable_semantics/testdata/generic_class/fail_bad_parameter_type.carbon b/executable_semantics/testdata/generic_class/fail_bad_parameter_type.carbon index f45787715719..4b148e0f9d0f 100644 --- a/executable_semantics/testdata/generic_class/fail_bad_parameter_type.carbon +++ b/executable_semantics/testdata/generic_class/fail_bad_parameter_type.carbon @@ -7,7 +7,7 @@ // RUN: %{not} %{executable_semantics} --parser_debug --trace_file=- %s 2>&1 | \ // RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s // AUTOUPDATE: %{executable_semantics} %s -// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/generic_class/fail_bad_parameter_type.carbon:15: unexpected type of deduced parameter i32 +// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/generic_class/fail_bad_parameter_type.carbon:28: type error in call: '(Type)' is not implicitly convertible to '(i32)' package ExecutableSemanticsTest api; diff --git a/executable_semantics/testdata/impl/fail_impl_as_not_constraint.carbon b/executable_semantics/testdata/impl/fail_impl_as_not_constraint.carbon new file mode 100644 index 000000000000..ed5b48f7cd6c --- /dev/null +++ b/executable_semantics/testdata/impl/fail_impl_as_not_constraint.carbon @@ -0,0 +1,17 @@ +// 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 +// +// RUN: %{not} %{executable_semantics} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{not} %{executable_semantics} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{executable_semantics} %s +// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/impl/fail_impl_as_not_constraint.carbon:14: expected constraint after `as`, found value of type i32 + +package ExecutableSemanticsTest api; + +external impl i32 as i32 {} + +fn Main() -> i32 { +} diff --git a/executable_semantics/testdata/impl/fail_impl_as_parameterized.carbon b/executable_semantics/testdata/impl/fail_impl_as_parameterized.carbon new file mode 100644 index 000000000000..a930878b59ff --- /dev/null +++ b/executable_semantics/testdata/impl/fail_impl_as_parameterized.carbon @@ -0,0 +1,20 @@ +// 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 +// +// RUN: %{not} %{executable_semantics} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{not} %{executable_semantics} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{executable_semantics} %s +// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/impl/fail_impl_as_parameterized.carbon:17: missing arguments for parameterized interface + +package ExecutableSemanticsTest api; + +interface Vector(Scalar:! Type) { +} + +external impl i32 as Vector {} + +fn Main() -> i32 { +} diff --git a/executable_semantics/testdata/impl/impl_as.carbon b/executable_semantics/testdata/impl/impl_as.carbon new file mode 100644 index 000000000000..3e8d3fb8055f --- /dev/null +++ b/executable_semantics/testdata/impl/impl_as.carbon @@ -0,0 +1,17 @@ +// 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 +// +// RUN: %{not} %{executable_semantics} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{not} %{executable_semantics} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{executable_semantics} %s +// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/impl/impl_as.carbon:14: expected constraint after `as`, found value of type i32 + +package ExecutableSemanticsTest api; + +external impl i32 as i32 {} + +fn Main() -> i32 { +} diff --git a/executable_semantics/testdata/interface/constrained_parameter.carbon b/executable_semantics/testdata/interface/constrained_parameter.carbon new file mode 100644 index 000000000000..08fd4b77f93b --- /dev/null +++ b/executable_semantics/testdata/interface/constrained_parameter.carbon @@ -0,0 +1,66 @@ +// 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 +// +// RUN: %{executable_semantics} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{executable_semantics} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{executable_semantics} %s +// CHECK: result: 0 + +package ExecutableSemanticsTest api; + +interface AddMul { + fn Add[me: Self](o: Self) -> Self; + fn Mul[me: Self](o: Self) -> Self; +} + +external impl i32 as AddMul { + fn Add[me: i32](o: i32) -> i32 { + return me + o; + } + fn Mul[me: i32](o: i32) -> i32 { + return me * o; + } +} + +class Holder(T:! AddMul) { + var v: T; +} + +interface Vector(Scalar:! AddMul) { + fn Zero() -> Self; + fn Add[me: Self](b: Self) -> Self; + fn Scale[me: Self](v: Scalar) -> Self; + fn Hold[me: Self](v: Scalar) -> Holder(Scalar); +} + +class Point { + var x: i32; + var y: i32; + impl Point as Vector(i32) { + fn Zero() -> Point { + return {.x = 0, .y = 0}; + } + fn Add[me: Point](b: Point) -> Point { + return {.x = me.x + b.x, .y = me.y + b.y}; + } + fn Scale[me: Point](v: i32) -> Point { + return {.x = me.x * v, .y = me.y * v}; + } + fn Hold[me: Point](v: i32) -> Holder(i32) { + return {.v = v}; + } + } +} + +fn AddAndScaleGeneric[T:! AddMul, U:! Vector(T)](a: U, s: T) -> U { + return a.Add(U.Zero()).Scale(a.Hold(s).v); +} + +fn Main() -> i32 { + var a: Point = {.x = 2, .y = 1}; + var p: Point = AddAndScaleGeneric(a, 5); + return p.x - 10; +} diff --git a/executable_semantics/testdata/interface/parameterized.carbon b/executable_semantics/testdata/interface/parameterized.carbon new file mode 100644 index 000000000000..c8549554180f --- /dev/null +++ b/executable_semantics/testdata/interface/parameterized.carbon @@ -0,0 +1,44 @@ +// 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 +// +// RUN: %{executable_semantics} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{executable_semantics} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{executable_semantics} %s +// CHECK: result: 0 + +package ExecutableSemanticsTest api; + +interface Vector(Scalar:! Type) { + fn Zero() -> Self; + fn Add[me: Self](b: Self) -> Self; + fn Scale[me: Self](v: Scalar) -> Self; +} + +class Point { + var x: i32; + var y: i32; + impl Point as Vector(i32) { + fn Zero() -> Point { + return {.x = 0, .y = 0}; + } + fn Add[me: Point](b: Point) -> Point { + return {.x = me.x + b.x, .y = me.y + b.y}; + } + fn Scale[me: Point](v: i32) -> Point { + return {.x = me.x * v, .y = me.y * v}; + } + } +} + +fn AddAndScaleGeneric[T:! Type, U:! Vector(T)](a: U, s: T) -> U { + return a.Add(U.Zero()).Scale(s); +} + +fn Main() -> i32 { + var a: Point = {.x = 2, .y = 1}; + var p: Point = AddAndScaleGeneric(a, 5); + return p.x - 10; +}