From c173aa653a49ed73b0753c3e4ee3d9c40279e21f Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 27 May 2022 14:38:18 -0700 Subject: [PATCH 01/11] Initial support for constraints and constraint formation with `&`. Very little works right now. --- explorer/ast/expression.cpp | 1 + explorer/ast/expression.h | 1 + explorer/interpreter/impl_scope.cpp | 44 ++++-- explorer/interpreter/impl_scope.h | 19 ++- explorer/interpreter/interpreter.cpp | 18 ++- explorer/interpreter/type_checker.cpp | 187 ++++++++++++++++++++++++-- explorer/interpreter/type_checker.h | 32 ++++- explorer/interpreter/value.cpp | 137 ++++++++++++++----- explorer/interpreter/value.h | 101 ++++++++++++-- explorer/syntax/parser.ypp | 16 +++ 10 files changed, 478 insertions(+), 78 deletions(-) diff --git a/explorer/ast/expression.cpp b/explorer/ast/expression.cpp index e2b3115479f49..2937f89cec5fe 100644 --- a/explorer/ast/expression.cpp +++ b/explorer/ast/expression.cpp @@ -55,6 +55,7 @@ auto ToString(Operator op) -> std::string_view { case Operator::Add: return "+"; case Operator::AddressOf: + case Operator::Combine: return "&"; case Operator::Neg: case Operator::Sub: diff --git a/explorer/ast/expression.h b/explorer/ast/expression.h index 9a61c28f66a76..2003e7adfc258 100644 --- a/explorer/ast/expression.h +++ b/explorer/ast/expression.h @@ -111,6 +111,7 @@ enum class Operator { Add, AddressOf, And, + Combine, Deref, Eq, Mul, diff --git a/explorer/interpreter/impl_scope.cpp b/explorer/interpreter/impl_scope.cpp index 427e50c537b68..e9d38b931f4ee 100644 --- a/explorer/interpreter/impl_scope.cpp +++ b/explorer/interpreter/impl_scope.cpp @@ -10,6 +10,7 @@ #include "llvm/Support/Casting.h" using llvm::cast; +using llvm::dyn_cast; namespace Carbon { @@ -34,10 +35,39 @@ void ImplScope::AddParent(Nonnull parent) { parent_scopes_.push_back(parent); } -auto ImplScope::Resolve(Nonnull iface_type, - Nonnull type, SourceLocation source_loc, +auto ImplScope::Resolve(Nonnull constraint_type, + Nonnull impl_type, + SourceLocation source_loc, const TypeChecker& type_checker) const -> ErrorOr> { + if (const auto *iface_type = dyn_cast(constraint_type)) { + return ResolveInterface(iface_type, impl_type, source_loc, type_checker); + } + if (const auto* constraint = dyn_cast(constraint_type)) { + std::vector> witnesses; + BindingMap map; + map[constraint->self_binding()] = impl_type; + for (auto impl : constraint->impl_constraints()) { + CARBON_ASSIGN_OR_RETURN( + Nonnull result, + ResolveInterface( + cast(type_checker.Substitute(map, impl.interface)), + type_checker.Substitute(map, impl.type), source_loc, + type_checker)); + witnesses.push_back(result); + } + // TODO: Check satisfaction of same-type constraints. + return type_checker.MakeConstraintWitness(*constraint, std::move(witnesses), + source_loc); + } + CARBON_FATAL() << "expected a constraint, not " << *constraint_type; +} + +auto ImplScope::ResolveInterface(Nonnull iface_type, + Nonnull type, + SourceLocation source_loc, + const TypeChecker& type_checker) const + -> ErrorOr> { CARBON_ASSIGN_OR_RETURN( std::optional> result, TryResolve(iface_type, type, source_loc, *this, type_checker)); @@ -48,7 +78,7 @@ auto ImplScope::Resolve(Nonnull iface_type, return *result; } -auto ImplScope::TryResolve(Nonnull iface_type, +auto ImplScope::TryResolve(Nonnull iface_type, Nonnull type, SourceLocation source_loc, const ImplScope& original_scope, @@ -73,20 +103,16 @@ auto ImplScope::TryResolve(Nonnull iface_type, return result; } -auto ImplScope::ResolveHere(Nonnull iface_type, +auto ImplScope::ResolveHere(Nonnull iface_type, Nonnull impl_type, SourceLocation source_loc, const ImplScope& original_scope, const TypeChecker& type_checker) const -> ErrorOr>> { - if (iface_type->kind() != Value::Kind::InterfaceType) { - CARBON_FATAL() << "expected an interface, not " << *iface_type; - } - const auto& iface = cast(*iface_type); std::optional> result = std::nullopt; for (const Impl& impl : impls_) { std::optional> m = type_checker.MatchImpl( - iface, impl_type, impl, original_scope, source_loc); + *iface_type, impl_type, impl, original_scope, source_loc); if (m.has_value()) { if (result.has_value()) { return CompilationError(source_loc) diff --git a/explorer/interpreter/impl_scope.h b/explorer/interpreter/impl_scope.h index 0505a2394039f..5c641eacfabd0 100644 --- a/explorer/interpreter/impl_scope.h +++ b/explorer/interpreter/impl_scope.h @@ -55,10 +55,10 @@ class ImplScope { // REQUIRES: `parent` is not already a parent of this scope. void AddParent(Nonnull parent); - // Returns the associated impl for the given `iface` and `type` in + // Returns the associated impl for the given `constraint` and `type` in // the ancestor graph of this scope, or reports a compilation error // at `source_loc` there isn't exactly one matching impl. - auto Resolve(Nonnull iface, Nonnull type, + auto Resolve(Nonnull constraint, Nonnull type, SourceLocation source_loc, const TypeChecker& type_checker) const -> ErrorOr>; @@ -81,14 +81,23 @@ class ImplScope { }; private: + // Returns the associated impl for the given `iface` and `type` in + // the ancestor graph of this scope, or reports a compilation error + // at `source_loc` there isn't exactly one matching impl. + auto ResolveInterface(Nonnull iface, + Nonnull type, SourceLocation source_loc, + const TypeChecker& type_checker) const + -> ErrorOr>; + // Returns the associated impl for the given `iface` and `type` in // the ancestor graph of this scope, returns std::nullopt if there // is none, or reports a compilation error is there is not a most // specific impl for the given `iface` and `type`. // Use `original_scope` to satisfy requirements of any generic impl // that matches `iface` and `type`. - auto TryResolve(Nonnull iface, Nonnull type, - SourceLocation source_loc, const ImplScope& original_scope, + auto TryResolve(Nonnull iface_type, + Nonnull type, SourceLocation source_loc, + const ImplScope& original_scope, const TypeChecker& type_checker) const -> ErrorOr>>; @@ -98,7 +107,7 @@ class ImplScope { // given `iface` and `type`. // Use `original_scope` to satisfy requirements of any generic impl // that matches `iface` and `type`. - auto ResolveHere(Nonnull iface_type, + auto ResolveHere(Nonnull iface_type, Nonnull impl_type, SourceLocation source_loc, const ImplScope& original_scope, const TypeChecker& type_checker) const diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index a2c96e89eed73..4d3167bb15bb6 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -84,7 +84,8 @@ class Interpreter { const std::vector>& values) -> Nonnull; - auto EvalPrim(Operator op, const std::vector>& args, + auto EvalPrim(Operator op, Nonnull static_type, + const std::vector>& args, SourceLocation source_loc) -> ErrorOr>; // Returns the result of converting `value` to type `destination_type`. @@ -159,6 +160,7 @@ void Interpreter::PrintState(llvm::raw_ostream& out) { } auto Interpreter::EvalPrim(Operator op, + Nonnull static_type, const std::vector>& args, SourceLocation source_loc) -> ErrorOr> { @@ -190,6 +192,8 @@ auto Interpreter::EvalPrim(Operator op, return heap_.Read(cast(*args[0]).address(), source_loc); case Operator::AddressOf: return arena_->New(cast(*args[0]).address()); + case Operator::Combine: + return &cast(static_type)->constraint_type(); } } @@ -464,7 +468,7 @@ auto Interpreter::InstantiateType(Nonnull type, CARBON_ASSIGN_OR_RETURN(inst_type_args[ty_var], InstantiateType(ty_arg, source_loc)); } - std::map, Nonnull> witnesses; + ImplWitnessMap witnesses; for (const auto& [bind, impl_exp] : class_type.impls()) { CARBON_ASSIGN_OR_RETURN(witnesses[bind], EvalImplExp(impl_exp)); } @@ -497,6 +501,7 @@ auto Interpreter::Convert(Nonnull value, case Value::Kind::AutoType: case Value::Kind::NominalClassType: case Value::Kind::InterfaceType: + case Value::Kind::ConstraintType: case Value::Kind::Witness: case Value::Kind::ParameterizedEntityName: case Value::Kind::ChoiceType: @@ -509,6 +514,7 @@ auto Interpreter::Convert(Nonnull value, case Value::Kind::StringValue: case Value::Kind::TypeOfClassType: case Value::Kind::TypeOfInterfaceType: + case Value::Kind::TypeOfConstraintType: case Value::Kind::TypeOfChoiceType: case Value::Kind::TypeOfParameterizedEntityName: case Value::Kind::TypeOfMemberName: @@ -936,9 +942,9 @@ auto Interpreter::StepExp() -> ErrorOr { } else { // { {v :: op(vs,[]) :: C, E, F} :: S, H} // -> { {eval_prim(op, (vs,v)) :: C, E, F} :: S, H} - CARBON_ASSIGN_OR_RETURN( - Nonnull value, - EvalPrim(op.op(), act.results(), exp.source_loc())); + CARBON_ASSIGN_OR_RETURN(Nonnull value, + EvalPrim(op.op(), &op.static_type(), + act.results(), exp.source_loc())); return todo_.FinishAction(value); } } @@ -968,7 +974,7 @@ auto Interpreter::StepExp() -> ErrorOr { if (num_impls > 0) { int i = 2; for (const auto& [impl_bind, impl_exp] : call.impls()) { - witnesses[impl_bind] = cast(act.results()[i]); + witnesses[impl_bind] = act.results()[i]; ++i; } } diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 6c8d759f7928b..e78e08acc4747 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -106,8 +106,10 @@ static auto IsTypeOfType(Nonnull value) -> bool { return false; case Value::Kind::TypeType: case Value::Kind::InterfaceType: + case Value::Kind::ConstraintType: case Value::Kind::TypeOfClassType: case Value::Kind::TypeOfInterfaceType: + case Value::Kind::TypeOfConstraintType: case Value::Kind::TypeOfChoiceType: // A value of one of these types is itself always a type. return true; @@ -149,12 +151,14 @@ static auto IsType(Nonnull value, bool concrete = false) -> bool { case Value::Kind::StructType: case Value::Kind::NominalClassType: case Value::Kind::InterfaceType: + case Value::Kind::ConstraintType: case Value::Kind::ChoiceType: case Value::Kind::ContinuationType: case Value::Kind::VariableType: case Value::Kind::StringType: case Value::Kind::TypeOfClassType: case Value::Kind::TypeOfInterfaceType: + case Value::Kind::TypeOfConstraintType: case Value::Kind::TypeOfChoiceType: case Value::Kind::StaticArrayType: return true; @@ -680,12 +684,14 @@ auto TypeChecker::ArgumentDeduction( // TODO: We could deduce the array type from an array or tuple argument. case Value::Kind::ContinuationType: case Value::Kind::ChoiceType: + case Value::Kind::ConstraintType: case Value::Kind::IntType: case Value::Kind::BoolType: case Value::Kind::TypeType: case Value::Kind::StringType: case Value::Kind::TypeOfClassType: case Value::Kind::TypeOfInterfaceType: + case Value::Kind::TypeOfConstraintType: case Value::Kind::TypeOfChoiceType: case Value::Kind::TypeOfParameterizedEntityName: case Value::Kind::TypeOfMemberName: @@ -722,6 +728,14 @@ auto TypeChecker::ArgumentDeduction( auto TypeChecker::Substitute( const std::map, Nonnull>& dict, Nonnull type) const -> Nonnull { + auto SubstituteIntoBindingMap = [&](const BindingMap& map) -> BindingMap { + BindingMap result; + for (const auto& [name, value] : map) { + result[name] = Substitute(dict, value); + } + return result; + }; + switch (type->kind()) { case Value::Kind::VariableType: { auto it = dict.find(&cast(*type).binding()); @@ -761,12 +775,10 @@ auto TypeChecker::Substitute( } case Value::Kind::NominalClassType: { const auto& class_type = cast(*type); - BindingMap type_args; - for (const auto& [name, value] : class_type.type_args()) { - type_args[name] = Substitute(dict, value); - } Nonnull new_class_type = - arena_->New(&class_type.declaration(), type_args); + arena_->New( + &class_type.declaration(), + SubstituteIntoBindingMap(class_type.type_args())); if (trace_stream_) { **trace_stream_ << "substitution: " << class_type << " => " << *new_class_type << "\n"; @@ -775,18 +787,57 @@ auto TypeChecker::Substitute( } 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); + Nonnull new_iface_type = arena_->New( + &iface_type.declaration(), + SubstituteIntoBindingMap(iface_type.args())); if (trace_stream_) { **trace_stream_ << "substitution: " << iface_type << " => " << *new_iface_type << "\n"; } return new_iface_type; } + case Value::Kind::ConstraintType: { + const auto& constraint = cast(*type); + std::vector impl_constraints; + impl_constraints.reserve(constraint.impl_constraints().size()); + for (const auto& impl_constraint : constraint.impl_constraints()) { + impl_constraints.push_back( + {.type = Substitute(dict, impl_constraint.type), + .interface = cast( + Substitute(dict, impl_constraint.interface))}); + } + + std::vector same_type_constraints; + same_type_constraints.reserve(constraint.same_type_constraints().size()); + for (const auto& same_type_constraint : + constraint.same_type_constraints()) { + std::vector> types; + for (const Value* type: same_type_constraint.types) { + types.push_back(Substitute(dict, type)); + } + same_type_constraints.push_back({.types = types}); + } + // TODO: Coalesce same-type constraints that are now overlapping. + + std::vector lookup_contexts; + lookup_contexts.reserve(constraint.lookup_contexts().size()); + for (const auto& lookup_context : + constraint.lookup_contexts()) { + lookup_contexts.push_back( + {.context = Substitute(dict, lookup_context.context)}); + } + // TODO: If the self_binding is substituted, should we track that + // somehow? + Nonnull new_constraint = + arena_->New( + constraint.self_binding(), std::move(impl_constraints), + std::move(same_type_constraints), std::move(lookup_contexts)); + if (trace_stream_) { + **trace_stream_ << "substitution: " << constraint << " => " + << *new_constraint << "\n"; + } + return new_constraint; + } case Value::Kind::StaticArrayType: case Value::Kind::AutoType: case Value::Kind::IntType: @@ -795,11 +846,15 @@ auto TypeChecker::Substitute( case Value::Kind::ChoiceType: case Value::Kind::ContinuationType: case Value::Kind::StringType: + return type; case Value::Kind::TypeOfClassType: case Value::Kind::TypeOfInterfaceType: + case Value::Kind::TypeOfConstraintType: case Value::Kind::TypeOfChoiceType: case Value::Kind::TypeOfParameterizedEntityName: case Value::Kind::TypeOfMemberName: + // TODO: We should substitute into the value and produce a new type of + // type for it. return type; case Value::Kind::Witness: case Value::Kind::ParameterizedEntityName: @@ -849,6 +904,10 @@ auto TypeChecker::MatchImpl(const InterfaceType& iface, return std::nullopt; } + // TODO: If the `interface` is a ConstraintType, for every impl_constraint, + // try deduction against that. + // TODO: How to handle the case where `iface` is itself a ConstraintType? + // Look up each impl_constraint individually? if (ErrorOr e = ArgumentDeduction( source_loc, "match", impl.deduced, deduced_args, impl.interface, &iface, /*allow_implicit_conversion=*/false, impl_scope); @@ -889,6 +948,14 @@ auto TypeChecker::MatchImpl(const InterfaceType& iface, source_loc, impl.impl, deduced_args, impls); } +auto TypeChecker::MakeConstraintWitness( + const ConstraintType& constraint, + std::vector> impl_constraint_witnesses, + SourceLocation source_loc) const -> Nonnull { + return arena_->New(source_loc, + std::move(impl_constraint_witnesses)); +} + auto TypeChecker::SatisfyImpls( llvm::ArrayRef> impl_bindings, const ImplScope& impl_scope, SourceLocation source_loc, @@ -907,6 +974,79 @@ auto TypeChecker::SatisfyImpls( return Success(); } +auto TypeChecker::MakeConstraintForInterface( + SourceLocation source_loc, + Nonnull iface_type) + -> Nonnull { + auto* self_binding = arena_->New( + source_loc, ".Self", arena_->New(source_loc)); + auto* self = arena_->New(self_binding); + std::vector impl_constraints = { + ConstraintType::ImplConstraint{.type = self, .interface = iface_type}}; + std::vector same_type_constraints = {}; + std::vector lookup_contexts = { + {.context = iface_type}}; + return arena_->New(self_binding, std::move(impl_constraints), + std::move(same_type_constraints), + std::move(lookup_contexts)); +} + +auto TypeChecker::CombineConstraints( + SourceLocation source_loc, + llvm::ArrayRef> constraints) + -> Nonnull { + auto* self_binding = arena_->New( + source_loc, ".Self", arena_->New(source_loc)); + auto* self = arena_->New(self_binding); + std::vector impl_constraints; + std::vector same_type_constraints; + std::vector lookup_contexts; + for (Nonnull constraint : constraints) { + BindingMap map; + map[constraint->self_binding()] = self; + // TODO: Remove duplicates + for (ConstraintType::ImplConstraint impl : constraint->impl_constraints()) { + impl_constraints.push_back( + {.type = Substitute(map, impl.type), + .interface = cast(Substitute(map, impl.interface))}); + } + for (ConstraintType::SameTypeConstraint same : + constraint->same_type_constraints()) { + std::vector> types; + for (const Value* type : same.types) { + types.push_back(Substitute(map, type)); + } + auto AddSameTypeConstraint = + [&](std::vector> types) { + // TODO: This is really inefficient. Use value canonicalization or + // hashing or similar to avoid the quadratic scan here. + for (const Value* type : types) { + for (ConstraintType::SameTypeConstraint& existing : + same_type_constraints) { + for (const Value* existing_type : existing.types) { + if (TypeEqual(type, existing_type)) { + // TODO: Remove duplicates + existing.types.insert(existing.types.end(), types.begin(), + types.end()); + return; + } + } + } + } + same_type_constraints.push_back({.types = std::move(types)}); + }; + AddSameTypeConstraint(std::move(types)); + } + // TODO: Remove duplicates + for (ConstraintType::LookupContext lookup : constraint->lookup_contexts()) { + lookup_contexts.push_back({.context = Substitute(map, lookup.context)}); + } + } + return arena_->New(self_binding, std::move(impl_constraints), + std::move(same_type_constraints), + std::move(lookup_contexts)); +} + auto TypeChecker::DeduceCallBindings( CallExpression& call, Nonnull params_type, llvm::ArrayRef generic_params, @@ -1526,6 +1666,27 @@ auto TypeChecker::TypeCheckExp(Nonnull e, op.set_static_type(arena_->New(ts[0])); op.set_value_category(ValueCategory::Let); return Success(); + case Operator::Combine: { + std::optional> constraints[2]; + for (int i : {0, 1}) { + if (auto* iface_type_type = dyn_cast(ts[i])) { + constraints[i] = MakeConstraintForInterface( + e->source_loc(), &iface_type_type->interface_type()); + } else if (auto* constraint_type_type = + dyn_cast(ts[i])) { + constraints[i] = &constraint_type_type->constraint_type(); + } else { + return CompilationError(op.arguments()[i]->source_loc()) + << "argument to " << ToString(op.op()) + << " should be a constraint, found `" << *ts[i] << "`"; + } + } + op.set_static_type( + arena_->New(CombineConstraints( + e->source_loc(), {*constraints[0], *constraints[1]}))); + op.set_value_category(ValueCategory::Let); + return Success(); + } } break; } @@ -1840,7 +2001,7 @@ auto TypeChecker::TypeCheckPattern( binding.set_symbolic_identity(val); SetValue(&binding, val); - if (isa(type)) { + if (isa(type)) { Nonnull impl_binding = arena_->New(binding.source_loc(), &binding, type); binding.set_impl_binding(impl_binding); @@ -2627,9 +2788,11 @@ static bool IsValidTypeForAliasTarget(Nonnull type) { case Value::Kind::FunctionType: case Value::Kind::InterfaceType: + case Value::Kind::ConstraintType: case Value::Kind::TypeType: case Value::Kind::TypeOfClassType: case Value::Kind::TypeOfInterfaceType: + case Value::Kind::TypeOfConstraintType: case Value::Kind::TypeOfChoiceType: case Value::Kind::TypeOfParameterizedEntityName: case Value::Kind::TypeOfMemberName: diff --git a/explorer/interpreter/type_checker.h b/explorer/interpreter/type_checker.h index a016da7dc5440..df513e48b6c85 100644 --- a/explorer/interpreter/type_checker.h +++ b/explorer/interpreter/type_checker.h @@ -49,6 +49,13 @@ class TypeChecker { Nonnull arg, bool allow_implicit_conversion, const ImplScope& impl_scope) const -> ErrorOr; + // Construct a type that is the same as `type` except that occurrences + // of type variables (aka. `GenericBinding`) are replaced by their + // corresponding type in `dict`. + auto Substitute(const std::map, + Nonnull>& dict, + Nonnull type) const -> Nonnull; + // If `impl` can be an implementation of interface `iface` for the // given `type`, then return an expression that will produce the witness // for this `impl` (at runtime). Otherwise return std::nullopt. @@ -57,6 +64,13 @@ class TypeChecker { SourceLocation source_loc) const -> std::optional>; + // Given the witnesses for the components of a constraint, form a witness for + // the constraint. + auto MakeConstraintWitness( + const ConstraintType& constraint, + std::vector> impl_constraint_witnesses, + SourceLocation source_loc) const -> Nonnull; + private: // Information about the currently enclosing scopes. struct ScopeInfo { @@ -305,13 +319,6 @@ class TypeChecker { BuiltinInterfaceName interface) const -> ErrorOr>; - // Construct a type that is the same as `type` except that occurrences - // of type variables (aka. `GenericBinding`) are replaced by their - // corresponding type in `dict`. - auto Substitute(const std::map, - Nonnull>& dict, - Nonnull type) const -> Nonnull; - // 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 @@ -321,6 +328,17 @@ class TypeChecker { const BindingMap& deduced_type_args, ImplExpMap& impls) const -> ErrorOr; + // Given an interface type, form a corresponding constraint type. + auto MakeConstraintForInterface(SourceLocation source_loc, + Nonnull iface_type) + -> Nonnull; + + // Given a list of constraint types, form the combined constraint. + auto CombineConstraints( + SourceLocation source_loc, + llvm::ArrayRef> constraints) + -> Nonnull; + // Sets value_node.constant_value() to `value`. Can be called multiple // times on the same value_node, so long as it is always called with // the same value. diff --git a/explorer/interpreter/value.cpp b/explorer/interpreter/value.cpp index fe28ac3a6dd30..eb63f24ae9fe7 100644 --- a/explorer/interpreter/value.cpp +++ b/explorer/interpreter/value.cpp @@ -198,6 +198,21 @@ auto Value::SetField(Nonnull arena, const FieldPath& path, field_value, source_loc); } +static auto PrintNameWithBindings(llvm::raw_ostream& out, + Nonnull declaration, + const BindingMap& args) { + out << GetName(*declaration).value_or("(anonymous)"); + // TODO: Print '()' if declaration is parameterized but no args are provided. + if (!args.empty()) { + out << "("; + llvm::ListSeparator sep; + for (const auto& [bind, val] : args) { + out << sep << bind->name() << " = " << *val; + } + out << ")"; + } +} + void Value::Print(llvm::raw_ostream& out) const { switch (kind()) { case Value::Kind::AlternativeConstructorValue: { @@ -313,15 +328,9 @@ void Value::Print(llvm::raw_ostream& out) const { } case Value::Kind::NominalClassType: { const auto& class_type = cast(*this); - out << "class " << class_type.declaration().name(); - if (!class_type.type_args().empty()) { - out << "("; - llvm::ListSeparator sep; - for (const auto& [bind, val] : class_type.type_args()) { - out << sep << bind->name() << " = " << *val; - } - out << ")"; - } + out << "class "; + PrintNameWithBindings(out, &class_type.declaration(), + class_type.type_args()); if (!class_type.impls().empty()) { out << " impls "; llvm::ListSeparator sep; @@ -340,14 +349,33 @@ 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 << "interface "; + PrintNameWithBindings(out, &iface_type.declaration(), iface_type.args()); + break; + } + case Value::Kind::ConstraintType: { + const auto& constraint = cast(*this); + out << "constraint "; + llvm::ListSeparator combine(" & "); + for (const ConstraintType::LookupContext &ctx : + constraint.lookup_contexts()) { + out << combine << *ctx.context; + } + out << " where "; + llvm::ListSeparator sep; + for (const ConstraintType::ImplConstraint &impl : + constraint.impl_constraints()) { + // TODO: Skip cases where `impl.type` is `.Self` and the interface is + // in `lookup_contexts()`. + out << sep << *impl.type << " is " << *impl.interface; + } + for (const ConstraintType::SameTypeConstraint &same_type : + constraint.same_type_constraints()) { + out << sep; + llvm::ListSeparator equal(" = "); + for (Nonnull type : same_type.types) { + out << equal << *type; } - out << ")"; } break; } @@ -408,6 +436,10 @@ void Value::Print(llvm::raw_ostream& out) const { .name() << ")"; break; + case Value::Kind::TypeOfConstraintType: + out << "typeof(" << cast(*this).constraint_type() + << ")"; + break; case Value::Kind::TypeOfChoiceType: out << "typeof(" << cast(*this).choice_type().name() << ")"; @@ -466,6 +498,18 @@ void ContinuationValue::StackFragment::Print(llvm::raw_ostream& out) const { out << "}"; } +// Check whether two binding maps, which are assumed to have the same keys, are +// equal. +static auto BindingMapEqual(const BindingMap& map1, const BindingMap& map2) -> bool { + CARBON_CHECK(map1.size() == map2.size()) << "maps should have same keys"; + for (const auto& [key, value] : map1) { + if (!ValueEqual(value, map2.at(key))) { + return false; + } + } + return true; +} + auto TypeEqual(Nonnull t1, Nonnull t2) -> bool { if (t1->kind() != t2->kind()) { return false; @@ -494,30 +538,58 @@ auto TypeEqual(Nonnull t1, Nonnull t2) -> bool { } return true; } - case Value::Kind::NominalClassType: - if (cast(*t1).declaration().name() != - cast(*t2).declaration().name()) { + case Value::Kind::NominalClassType: { + const auto& class1 = cast(*t1); + const auto& class2 = cast(*t2); + return class1.declaration().name() == class2.declaration().name() && + BindingMapEqual(class1.type_args(), class2.type_args()); + } + case Value::Kind::InterfaceType: { + const auto& iface1 = cast(*t1); + const auto& iface2 = cast(*t2); + return iface1.declaration().name() == iface2.declaration().name() && + BindingMapEqual(iface1.args(), iface2.args()); + } + case Value::Kind::ConstraintType: { + const auto& constraint1 = cast(*t1); + const auto& constraint2 = cast(*t2); + if (constraint1.impl_constraints().size() != + constraint2.impl_constraints().size() || + constraint1.same_type_constraints().size() != + constraint2.same_type_constraints().size() || + constraint1.lookup_contexts().size() != + constraint2.lookup_contexts().size()) { return false; } - for (const auto& [ty_var1, ty1] : - cast(*t1).type_args()) { - if (!ValueEqual(ty1, - cast(*t2).type_args().at(ty_var1))) { + for (size_t i = 0; i < constraint1.impl_constraints().size(); ++i) { + const auto& impl1 = constraint1.impl_constraints()[i]; + const auto& impl2 = constraint2.impl_constraints()[i]; + if (!TypeEqual(impl1.type, impl2.type) || + !TypeEqual(impl1.interface, impl2.interface)) { return false; } } - return true; - case Value::Kind::InterfaceType: - if (cast(*t1).declaration().name() != - cast(*t2).declaration().name()) { - return false; + for (size_t i = 0; i < constraint1.same_type_constraints().size(); ++i) { + const auto& same_type1 = constraint1.same_type_constraints()[i]; + const auto& same_type2 = constraint2.same_type_constraints()[i]; + if (same_type1.types.size() != same_type2.types.size()) { + return false; + } + for (size_t j = 0; j < same_type1.types.size(); ++j) { + if (!TypeEqual(same_type1.types[i], same_type2.types[i])) { + return false; + } + } } - for (const auto& [ty_var1, ty1] : cast(*t1).args()) { - if (!ValueEqual(ty1, cast(*t2).args().at(ty_var1))) { + for (size_t i = 0; i < constraint1.lookup_contexts().size(); ++i) { + const auto& context1 = constraint1.lookup_contexts()[i]; + const auto& context2 = constraint2.lookup_contexts()[i]; + if (!TypeEqual(context1.context, context2.context)) { return false; } } return true; + } case Value::Kind::ChoiceType: return cast(*t1).name() == cast(*t2).name(); case Value::Kind::TupleValue: { @@ -548,6 +620,9 @@ auto TypeEqual(Nonnull t1, Nonnull t2) -> bool { case Value::Kind::TypeOfInterfaceType: return TypeEqual(&cast(*t1).interface_type(), &cast(*t2).interface_type()); + case Value::Kind::TypeOfConstraintType: + return TypeEqual(&cast(*t1).constraint_type(), + &cast(*t2).constraint_type()); case Value::Kind::TypeOfChoiceType: return TypeEqual(&cast(*t1).choice_type(), &cast(*t2).choice_type()); @@ -664,6 +739,7 @@ auto ValueEqual(Nonnull v1, Nonnull v2) -> bool { case Value::Kind::StructType: case Value::Kind::NominalClassType: case Value::Kind::InterfaceType: + case Value::Kind::ConstraintType: case Value::Kind::Witness: case Value::Kind::ChoiceType: case Value::Kind::ContinuationType: @@ -671,6 +747,7 @@ auto ValueEqual(Nonnull v1, Nonnull v2) -> bool { case Value::Kind::StringType: case Value::Kind::TypeOfClassType: case Value::Kind::TypeOfInterfaceType: + case Value::Kind::TypeOfConstraintType: case Value::Kind::TypeOfChoiceType: case Value::Kind::TypeOfParameterizedEntityName: case Value::Kind::TypeOfMemberName: diff --git a/explorer/interpreter/value.h b/explorer/interpreter/value.h index 97ca4a3df34bf..343f35b33e03a 100644 --- a/explorer/interpreter/value.h +++ b/explorer/interpreter/value.h @@ -55,6 +55,7 @@ class Value { StructType, NominalClassType, InterfaceType, + ConstraintType, ChoiceType, ContinuationType, // The type of a continuation. VariableType, // e.g., generic type parameters. @@ -67,6 +68,7 @@ class Value { StringValue, TypeOfClassType, TypeOfInterfaceType, + TypeOfConstraintType, TypeOfChoiceType, TypeOfParameterizedEntityName, TypeOfMemberName, @@ -130,7 +132,7 @@ class IntValue : public Value { }; using ImplWitnessMap = - std::map, Nonnull>; + std::map, Nonnull>; // A function value. class FunctionValue : public Value { @@ -156,10 +158,7 @@ class FunctionValue : public Value { auto type_args() const -> const BindingMap& { return type_args_; } - auto witnesses() const - -> const std::map, const Witness*>& { - return witnesses_; - } + auto witnesses() const -> const ImplWitnessMap& { return witnesses_; } private: Nonnull declaration_; @@ -179,8 +178,7 @@ class BoundMethodValue : public Value { explicit BoundMethodValue(Nonnull declaration, Nonnull receiver, const BindingMap& type_args, - const std::map, - Nonnull>& wits) + const ImplWitnessMap& wits) : Value(Kind::BoundMethodValue), declaration_(declaration), receiver_(receiver), @@ -561,8 +559,7 @@ class NominalClassType : public Value { // run-time type of an object. explicit NominalClassType(Nonnull declaration, const BindingMap& type_args, - const std::map, - Nonnull>& wits) + const ImplWitnessMap& wits) : Value(Kind::NominalClassType), declaration_(declaration), type_args_(type_args), @@ -611,6 +608,7 @@ auto FindMember(const std::string& name, -> std::optional>; // An interface type. +// TODO: Consider removing this once ConstraintType is ready. class InterfaceType : public Value { public: explicit InterfaceType(Nonnull declaration) @@ -654,6 +652,74 @@ class InterfaceType : public Value { ImplWitnessMap witnesses_; }; +// A type-of-type for an unknown constrained type. +// +// A constraint has three main properties: +// +// * A collection of (type, interface) pairs for interfaces that are known to +// be implemented by a type satisfying the constraint. +// * A collection of sets of types that are known to be the same. +// * A collection of contexts in which member name lookups will be performed +// for a type variable whose type is this constraint. +// +// Within these properties, the constrained type can be referred to with a +// `VariableType` naming the `self_binding`. +class ConstraintType : public Value { + public: + // A required implementation of an interface. + struct ImplConstraint { + Nonnull type; + Nonnull interface; + }; + + // A collection of types that are known to be the same. + struct SameTypeConstraint { + std::vector> types; + }; + + // A context in which we might look up a name. + struct LookupContext { + Nonnull context; + }; + + public: + explicit ConstraintType(Nonnull self_binding, + std::vector impl_constraints, + std::vector same_type_constraints, + std::vector lookup_contexts) + : Value(Kind::ConstraintType), + self_binding_(self_binding), + impl_constraints_(std::move(impl_constraints)), + same_type_constraints_(std::move(same_type_constraints)), + lookup_contexts_(std::move(lookup_contexts)) {} + + static auto classof(const Value* value) -> bool { + return value->kind() == Kind::ConstraintType; + } + + auto self_binding() const -> Nonnull { + return self_binding_; + } + + auto impl_constraints() const -> llvm::ArrayRef { + return impl_constraints_; + } + + auto same_type_constraints() const -> llvm::ArrayRef { + return same_type_constraints_; + } + + auto lookup_contexts() const -> llvm::ArrayRef { + return lookup_contexts_; + } + + private: + Nonnull self_binding_; + std::vector impl_constraints_; + std::vector same_type_constraints_; + std::vector lookup_contexts_; +}; + // The witness table for an impl. class Witness : public Value { public: @@ -941,6 +1007,23 @@ class TypeOfInterfaceType : public Value { Nonnull iface_type_; }; +class TypeOfConstraintType : public Value { + public: + explicit TypeOfConstraintType(Nonnull constraint_type) + : Value(Kind::TypeOfConstraintType), constraint_type_(constraint_type) {} + + static auto classof(const Value* value) -> bool { + return value->kind() == Kind::TypeOfConstraintType; + } + + auto constraint_type() const -> const ConstraintType& { + return *constraint_type_; + } + + private: + Nonnull constraint_type_; +}; + // The type of an expression whose value is a choice type. Currently there is no // way to explicitly name such a type in Carbon code, but we are tentatively // using `typeof(ChoiceName)` as the debug-printing format, in anticipation of diff --git a/explorer/syntax/parser.ypp b/explorer/syntax/parser.ypp index db9fd4f0757b5..4839916f6ddcf 100644 --- a/explorer/syntax/parser.ypp +++ b/explorer/syntax/parser.ypp @@ -118,6 +118,8 @@ %type > primary_expression %type > postfix_expression %type > ref_deref_expression +%type > combine_lhs +%type > combine_expression %type > type_expression %type > fn_type_expression %type > minus_expression @@ -380,8 +382,21 @@ fn_type_expression: FN_TYPE tuple ARROW type_expression { $$ = arena->New(context.source_loc(), $2, $4); } ; +combine_lhs: + ref_deref_expression +| combine_expression +; +combine_expression: + combine_lhs AMPERSAND ref_deref_expression + { + $$ = arena->New( + context.source_loc(), Operator::Combine, + std::vector>({$1, $3})); + } +; type_expression: ref_deref_expression +| combine_expression | fn_type_expression ; minus_expression: @@ -444,6 +459,7 @@ unimpl_expression: value_expression: // ref_deref_expression excluded due to precedence diamond. additive_expression +| combine_expression | fn_type_expression | unimpl_expression ; From 004ace0407ddc356ff3d2f3447711f3c2fa9c486 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 27 May 2022 18:01:46 -0700 Subject: [PATCH 02/11] Split a constraint into individual interfaces when adding it to the scope. This allows us to later find the sub-impls and pull out the right witness. --- explorer/ast/expression.h | 7 +-- explorer/interpreter/impl_scope.cpp | 24 +++++++-- explorer/interpreter/impl_scope.h | 6 +-- explorer/interpreter/interpreter.cpp | 27 ++++++---- explorer/interpreter/type_checker.cpp | 71 ++++++++++++++++++++++++--- explorer/interpreter/type_checker.h | 6 +++ 6 files changed, 114 insertions(+), 27 deletions(-) diff --git a/explorer/ast/expression.h b/explorer/ast/expression.h index 2003e7adfc258..366d46e587574 100644 --- a/explorer/ast/expression.h +++ b/explorer/ast/expression.h @@ -173,20 +173,21 @@ class SimpleMemberAccessExpression : public Expression { // If `object` has a generic type, returns the `ImplBinding` that // identifies its witness table. Otherwise, returns `std::nullopt`. Should not // be called before typechecking. - auto impl() const -> std::optional> { + auto impl() const -> std::optional> { return impl_; } // Can only be called once, during typechecking. - void set_impl(Nonnull impl) { + void set_impl(Nonnull impl) { CARBON_CHECK(!impl_.has_value()); impl_ = impl; } + private: Nonnull object_; std::string member_; - std::optional> impl_; + std::optional> impl_; }; // A compound member access expression of the form `object.(path)`. diff --git a/explorer/interpreter/impl_scope.cpp b/explorer/interpreter/impl_scope.cpp index e9d38b931f4ee..7320939b72c94 100644 --- a/explorer/interpreter/impl_scope.cpp +++ b/explorer/interpreter/impl_scope.cpp @@ -15,20 +15,34 @@ using llvm::dyn_cast; namespace Carbon { void ImplScope::Add(Nonnull iface, Nonnull type, - Nonnull impl) { - Add(iface, {}, type, {}, impl); + Nonnull impl, + const TypeChecker& type_checker) { + Add(iface, {}, type, {}, impl, type_checker); } void ImplScope::Add(Nonnull iface, llvm::ArrayRef> deduced, Nonnull type, llvm::ArrayRef> impl_bindings, - Nonnull impl) { - impls_.push_back({.interface = iface, + Nonnull impl_expr, + const TypeChecker& type_checker) { + if (auto *constraint = dyn_cast(iface)) { + BindingMap map; + map[constraint->self_binding()] = type; + for (size_t i = 0; i != constraint->impl_constraints().size(); ++i) { + ConstraintType::ImplConstraint impl = constraint->impl_constraints()[i]; + Add(cast(type_checker.Substitute(map, impl.interface)), + deduced, type_checker.Substitute(map, impl.type), impl_bindings, + type_checker.MakeConstraintWitnessAccess(impl_expr, i), type_checker); + } + return; + } + + impls_.push_back({.interface = cast(iface), .deduced = deduced, .type = type, .impl_bindings = impl_bindings, - .impl = impl}); + .impl = impl_expr}); } void ImplScope::AddParent(Nonnull parent) { diff --git a/explorer/interpreter/impl_scope.h b/explorer/interpreter/impl_scope.h index 5c641eacfabd0..60fbd0ffa96e6 100644 --- a/explorer/interpreter/impl_scope.h +++ b/explorer/interpreter/impl_scope.h @@ -42,14 +42,14 @@ class ImplScope { public: // Associates `iface` and `type` with the `impl` in this scope. void Add(Nonnull iface, Nonnull type, - Nonnull impl); + Nonnull impl, const TypeChecker& type_checker); // For a parameterized impl, associates `iface` and `type` // with the `impl` in this scope. void Add(Nonnull iface, llvm::ArrayRef> deduced, Nonnull type, llvm::ArrayRef> impl_bindings, - Nonnull impl); + Nonnull impl, const TypeChecker& type_checker); // Make `parent` a parent of this scope. // REQUIRES: `parent` is not already a parent of this scope. @@ -73,7 +73,7 @@ class ImplScope { // are non-empty. The former contains the type parameters and the // later are impl bindings, that is, parameters for witnesses. struct Impl { - Nonnull interface; + Nonnull interface; std::vector> deduced; Nonnull type; std::vector> impl_bindings; diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 4d3167bb15bb6..f65183c23af56 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -809,10 +809,19 @@ auto Interpreter::StepExp() -> ErrorOr { } case ExpressionKind::SimpleMemberAccessExpression: { const auto& access = cast(exp); + bool forming_member_name = isa(&access.static_type()); if (act.pos() == 0) { + // First, evaluate the first operand. return todo_.Spawn( std::make_unique(&access.object())); + } else if (act.pos() == 1 && access.impl().has_value() && + !forming_member_name) { + // Next, if we're accessing an interface member, evaluate the `impl` + // expression to find the corresponding witness. + return todo_.Spawn( + std::make_unique(access.impl().value())); } else { + // Finally, produce the result. if (const auto* member_name_type = dyn_cast(&access.static_type())) { // The result is a member name, such as in `Type.field_name`. Form a @@ -826,8 +835,13 @@ auto Interpreter::StepExp() -> ErrorOr { } else { type_result = act.results()[0]; if (access.impl().has_value()) { - iface_result = - cast(access.impl().value()->interface()); + // TODO: A Witness should know the interface type it's a witness + // for. + // FIXME: A lot still to do here. + Nonnull witness = cast(act.results()[1]); + Nonnull iface = + witness->declaration().interface_type(); // better hope it's not parameterized + iface_result = cast(iface); // better hope this isn't a constraint } } MemberName* member_name = arena_->New( @@ -838,14 +852,7 @@ auto Interpreter::StepExp() -> ErrorOr { // `value.field_name`. Extract the value within the given object. std::optional> witness; if (access.impl().has_value()) { - CARBON_ASSIGN_OR_RETURN( - auto witness_addr, - todo_.ValueOfNode(*access.impl(), access.source_loc())); - CARBON_ASSIGN_OR_RETURN( - Nonnull witness_value, - heap_.Read(llvm::cast(witness_addr)->address(), - access.source_loc())); - witness = cast(witness_value); + witness = cast(act.results()[1]); } FieldPath::Component member(access.member(), witness); CARBON_ASSIGN_OR_RETURN( diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index e78e08acc4747..3a1a81f8110d8 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -906,8 +906,6 @@ auto TypeChecker::MatchImpl(const InterfaceType& iface, // TODO: If the `interface` is a ConstraintType, for every impl_constraint, // try deduction against that. - // TODO: How to handle the case where `iface` is itself a ConstraintType? - // Look up each impl_constraint individually? if (ErrorOr e = ArgumentDeduction( source_loc, "match", impl.deduced, deduced_args, impl.interface, &iface, /*allow_implicit_conversion=*/false, impl_scope); @@ -956,6 +954,15 @@ auto TypeChecker::MakeConstraintWitness( std::move(impl_constraint_witnesses)); } +auto TypeChecker::MakeConstraintWitnessAccess( + Nonnull witness, size_t impl_offset) const + -> Nonnull { + return arena_->New( + witness->source_loc(), + witness, + arena_->New(witness->source_loc(), impl_offset)); +} + auto TypeChecker::SatisfyImpls( llvm::ArrayRef> impl_bindings, const ImplScope& impl_scope, SourceLocation source_loc, @@ -1379,7 +1386,8 @@ auto TypeChecker::TypeCheckExp(Nonnull e, Substitute(binding_map, &member_type); access.set_static_type(inst_member_type); CARBON_CHECK(var_type.binding().impl_binding().has_value()); - access.set_impl(*var_type.binding().impl_binding()); + access.set_impl( + CreateImplReference(*var_type.binding().impl_binding())); return Success(); } else { return CompilationError(e->source_loc()) @@ -1388,6 +1396,56 @@ auto TypeChecker::TypeCheckExp(Nonnull e, } break; } + case Value::Kind::ConstraintType: { + const auto& constraint = cast(typeof_var); + std::optional> found_in; + for (ConstraintType::LookupContext ctx : constraint.lookup_contexts()) { + BindingMap constraint_self_map; + constraint_self_map[constraint.self_binding()] = &var_type; + Nonnull resolved_context = + Substitute(constraint_self_map, ctx.context); + if (!isa(resolved_context)) { + // TODO: Support other kinds of lookup context, notably named + // constraints. + continue; + } + const auto& iface_type = cast(*resolved_context); + const InterfaceDeclaration& iface_decl = iface_type.declaration(); + if (std::optional> member = + FindMember(access.member(), iface_decl.members()); + member.has_value()) { + if (found_in.has_value() && + !ValueEqual(found_in.value(), resolved_context)) { + // TODO: If we resolve to the same member either way, this + // is not ambiguous. + return CompilationError(e->source_loc()) + << "ambiguous member access, " << access.member() + << " found in " << *found_in.value() << " and " + << *resolved_context; + } + 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(binding_map, &member_type); + access.set_static_type(inst_member_type); + // TODO: We could dig this out of the witness for the impl + // binding; it should always be there. + CARBON_ASSIGN_OR_RETURN( + Nonnull impl, + impl_scope.Resolve(&iface_type, &var_type, e->source_loc(), + *this)); + access.set_impl(impl); + found_in = resolved_context; + } + } + if (!found_in) { + return CompilationError(e->source_loc()) + << "member access, " << access.member() << " not in " + << constraint; + } + return Success(); + } default: return CompilationError(e->source_loc()) << "member access, unexpected " << object_type @@ -1410,7 +1468,8 @@ auto TypeChecker::TypeCheckExp(Nonnull e, FindMember(access.member(), iface_decl.members()); member.has_value()) { CARBON_CHECK(var_type.binding().impl_binding().has_value()); - access.set_impl(*var_type.binding().impl_binding()); + access.set_impl( + CreateImplReference(*var_type.binding().impl_binding())); switch ((*member)->kind()) { case DeclarationKind::FunctionDeclaration: { @@ -1912,7 +1971,7 @@ void TypeChecker::BringImplIntoScope(Nonnull impl_binding, CARBON_CHECK(impl_binding->type_var()->symbolic_identity().has_value()); impl_scope.Add(impl_binding->interface(), *impl_binding->type_var()->symbolic_identity(), - CreateImplReference(impl_binding)); + CreateImplReference(impl_binding), *this); } auto TypeChecker::TypeCheckTypeExp(Nonnull type_expression, @@ -2670,7 +2729,7 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull impl_decl, impl_decl->deduced_parameters().end()); scope_info.innermost_non_class_scope->Add( iface_type, std::move(deduced_bindings), impl_type_value, impl_bindings, - impl_id); + impl_id, *this); } // Declare the impl members. diff --git a/explorer/interpreter/type_checker.h b/explorer/interpreter/type_checker.h index df513e48b6c85..4fc287be13e89 100644 --- a/explorer/interpreter/type_checker.h +++ b/explorer/interpreter/type_checker.h @@ -71,6 +71,12 @@ class TypeChecker { std::vector> impl_constraint_witnesses, SourceLocation source_loc) const -> Nonnull; + // Given the witnesses for the components of a constraint, form a witness for + // the constraint. + auto MakeConstraintWitnessAccess(Nonnull witness, + size_t impl_offset) const + -> Nonnull; + private: // Information about the currently enclosing scopes. struct ScopeInfo { From 79508c8dba61140b19f5713b3744e1c529cdf106 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 27 May 2022 18:05:05 -0700 Subject: [PATCH 03/11] Reformat --- explorer/ast/expression.h | 1 - explorer/interpreter/impl_scope.cpp | 4 ++-- explorer/interpreter/interpreter.cpp | 9 +++++---- explorer/interpreter/type_checker.cpp | 27 +++++++++++++-------------- explorer/interpreter/value.cpp | 9 +++++---- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/explorer/ast/expression.h b/explorer/ast/expression.h index 366d46e587574..c30420a56e299 100644 --- a/explorer/ast/expression.h +++ b/explorer/ast/expression.h @@ -183,7 +183,6 @@ class SimpleMemberAccessExpression : public Expression { impl_ = impl; } - private: Nonnull object_; std::string member_; diff --git a/explorer/interpreter/impl_scope.cpp b/explorer/interpreter/impl_scope.cpp index 7320939b72c94..3bf73c5b6f1aa 100644 --- a/explorer/interpreter/impl_scope.cpp +++ b/explorer/interpreter/impl_scope.cpp @@ -26,7 +26,7 @@ void ImplScope::Add(Nonnull iface, llvm::ArrayRef> impl_bindings, Nonnull impl_expr, const TypeChecker& type_checker) { - if (auto *constraint = dyn_cast(iface)) { + if (auto* constraint = dyn_cast(iface)) { BindingMap map; map[constraint->self_binding()] = type; for (size_t i = 0; i != constraint->impl_constraints().size(); ++i) { @@ -54,7 +54,7 @@ auto ImplScope::Resolve(Nonnull constraint_type, SourceLocation source_loc, const TypeChecker& type_checker) const -> ErrorOr> { - if (const auto *iface_type = dyn_cast(constraint_type)) { + if (const auto* iface_type = dyn_cast(constraint_type)) { return ResolveInterface(iface_type, impl_type, source_loc, type_checker); } if (const auto* constraint = dyn_cast(constraint_type)) { diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index f65183c23af56..f40e7f86669d1 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -159,8 +159,7 @@ void Interpreter::PrintState(llvm::raw_ostream& out) { out << "\n}\n"; } -auto Interpreter::EvalPrim(Operator op, - Nonnull static_type, +auto Interpreter::EvalPrim(Operator op, Nonnull static_type, const std::vector>& args, SourceLocation source_loc) -> ErrorOr> { @@ -840,8 +839,10 @@ auto Interpreter::StepExp() -> ErrorOr { // FIXME: A lot still to do here. Nonnull witness = cast(act.results()[1]); Nonnull iface = - witness->declaration().interface_type(); // better hope it's not parameterized - iface_result = cast(iface); // better hope this isn't a constraint + witness->declaration() + .interface_type(); // better hope it's not parameterized + iface_result = cast( + iface); // better hope this isn't a constraint } } MemberName* member_name = arena_->New( diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 3a1a81f8110d8..5bf197d1105af 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -812,7 +812,7 @@ auto TypeChecker::Substitute( for (const auto& same_type_constraint : constraint.same_type_constraints()) { std::vector> types; - for (const Value* type: same_type_constraint.types) { + for (const Value* type : same_type_constraint.types) { types.push_back(Substitute(dict, type)); } same_type_constraints.push_back({.types = types}); @@ -821,8 +821,7 @@ auto TypeChecker::Substitute( std::vector lookup_contexts; lookup_contexts.reserve(constraint.lookup_contexts().size()); - for (const auto& lookup_context : - constraint.lookup_contexts()) { + for (const auto& lookup_context : constraint.lookup_contexts()) { lookup_contexts.push_back( {.context = Substitute(dict, lookup_context.context)}); } @@ -954,12 +953,11 @@ auto TypeChecker::MakeConstraintWitness( std::move(impl_constraint_witnesses)); } -auto TypeChecker::MakeConstraintWitnessAccess( - Nonnull witness, size_t impl_offset) const +auto TypeChecker::MakeConstraintWitnessAccess(Nonnull witness, + size_t impl_offset) const -> Nonnull { return arena_->New( - witness->source_loc(), - witness, + witness->source_loc(), witness, arena_->New(witness->source_loc(), impl_offset)); } @@ -982,8 +980,7 @@ auto TypeChecker::SatisfyImpls( } auto TypeChecker::MakeConstraintForInterface( - SourceLocation source_loc, - Nonnull iface_type) + SourceLocation source_loc, Nonnull iface_type) -> Nonnull { auto* self_binding = arena_->New( source_loc, ".Self", arena_->New(source_loc)); @@ -999,7 +996,7 @@ auto TypeChecker::MakeConstraintForInterface( } auto TypeChecker::CombineConstraints( - SourceLocation source_loc, + SourceLocation source_loc, llvm::ArrayRef> constraints) -> Nonnull { auto* self_binding = arena_->New( @@ -1399,7 +1396,8 @@ auto TypeChecker::TypeCheckExp(Nonnull e, case Value::Kind::ConstraintType: { const auto& constraint = cast(typeof_var); std::optional> found_in; - for (ConstraintType::LookupContext ctx : constraint.lookup_contexts()) { + for (ConstraintType::LookupContext ctx : + constraint.lookup_contexts()) { BindingMap constraint_self_map; constraint_self_map[constraint.self_binding()] = &var_type; Nonnull resolved_context = @@ -1410,7 +1408,8 @@ auto TypeChecker::TypeCheckExp(Nonnull e, continue; } const auto& iface_type = cast(*resolved_context); - const InterfaceDeclaration& iface_decl = iface_type.declaration(); + const InterfaceDeclaration& iface_decl = + iface_type.declaration(); if (std::optional> member = FindMember(access.member(), iface_decl.members()); member.has_value()) { @@ -1433,8 +1432,8 @@ auto TypeChecker::TypeCheckExp(Nonnull e, // binding; it should always be there. CARBON_ASSIGN_OR_RETURN( Nonnull impl, - impl_scope.Resolve(&iface_type, &var_type, e->source_loc(), - *this)); + impl_scope.Resolve(&iface_type, &var_type, + e->source_loc(), *this)); access.set_impl(impl); found_in = resolved_context; } diff --git a/explorer/interpreter/value.cpp b/explorer/interpreter/value.cpp index eb63f24ae9fe7..b66bb41e6ed3c 100644 --- a/explorer/interpreter/value.cpp +++ b/explorer/interpreter/value.cpp @@ -357,19 +357,19 @@ void Value::Print(llvm::raw_ostream& out) const { const auto& constraint = cast(*this); out << "constraint "; llvm::ListSeparator combine(" & "); - for (const ConstraintType::LookupContext &ctx : + for (const ConstraintType::LookupContext& ctx : constraint.lookup_contexts()) { out << combine << *ctx.context; } out << " where "; llvm::ListSeparator sep; - for (const ConstraintType::ImplConstraint &impl : + for (const ConstraintType::ImplConstraint& impl : constraint.impl_constraints()) { // TODO: Skip cases where `impl.type` is `.Self` and the interface is // in `lookup_contexts()`. out << sep << *impl.type << " is " << *impl.interface; } - for (const ConstraintType::SameTypeConstraint &same_type : + for (const ConstraintType::SameTypeConstraint& same_type : constraint.same_type_constraints()) { out << sep; llvm::ListSeparator equal(" = "); @@ -500,7 +500,8 @@ void ContinuationValue::StackFragment::Print(llvm::raw_ostream& out) const { // Check whether two binding maps, which are assumed to have the same keys, are // equal. -static auto BindingMapEqual(const BindingMap& map1, const BindingMap& map2) -> bool { +static auto BindingMapEqual(const BindingMap& map1, const BindingMap& map2) + -> bool { CARBON_CHECK(map1.size() == map2.size()) << "maps should have same keys"; for (const auto& [key, value] : map1) { if (!ValueEqual(value, map2.at(key))) { From d7ef77deab26b079a335f5ae78436b87e1393461 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 27 May 2022 18:13:16 -0700 Subject: [PATCH 04/11] Add test and fix test crash. --- explorer/interpreter/interpreter.cpp | 4 +-- .../constraint/combined_interfaces.carbon | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 explorer/testdata/constraint/combined_interfaces.carbon diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index f40e7f86669d1..b4fa2aa69b6c9 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -808,13 +808,11 @@ auto Interpreter::StepExp() -> ErrorOr { } case ExpressionKind::SimpleMemberAccessExpression: { const auto& access = cast(exp); - bool forming_member_name = isa(&access.static_type()); if (act.pos() == 0) { // First, evaluate the first operand. return todo_.Spawn( std::make_unique(&access.object())); - } else if (act.pos() == 1 && access.impl().has_value() && - !forming_member_name) { + } else if (act.pos() == 1 && access.impl().has_value()) { // Next, if we're accessing an interface member, evaluate the `impl` // expression to find the corresponding witness. return todo_.Spawn( diff --git a/explorer/testdata/constraint/combined_interfaces.carbon b/explorer/testdata/constraint/combined_interfaces.carbon new file mode 100644 index 0000000000000..22982a6b7aa0e --- /dev/null +++ b/explorer/testdata/constraint/combined_interfaces.carbon @@ -0,0 +1,31 @@ +// 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: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 3 + +package ExplorerTest api; + +interface A { fn F() -> i32; fn G() -> i32; } +interface B { fn H() -> i32; } + +fn Get1[T:! A & B](n: T) -> i32 { return n.F(); } +fn Get2[T:! B & A](n: T) -> i32 { return n.G(); } + +impl i32 as A { + fn F() -> i32 { return 1; } + fn G() -> i32 { return 2; } +} +impl i32 as B { + fn H() -> i32 { return 4; } +} + +fn Main() -> i32 { + var z: i32 = 0; + return Get1(z) + Get2(z); +} From b7cae2ebfbc1add9178d00b83ea0ffcfa8ab3b18 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 1 Jun 2022 14:10:26 -0700 Subject: [PATCH 05/11] Fix member lookup into type parameters. --- explorer/ast/expression.h | 16 ++++++++++++++++ explorer/interpreter/interpreter.cpp | 20 ++++++++------------ explorer/interpreter/type_checker.cpp | 1 + 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/explorer/ast/expression.h b/explorer/ast/expression.h index c30420a56e299..d78929fc214b2 100644 --- a/explorer/ast/expression.h +++ b/explorer/ast/expression.h @@ -26,6 +26,7 @@ namespace Carbon { class Value; class MemberName; class VariableType; +class InterfaceType; class ImplBinding; class Expression : public AstNode { @@ -183,10 +184,25 @@ class SimpleMemberAccessExpression : public Expression { impl_ = impl; } + // If `object` is a constrained type parameter and `member` was found in an + // interface, returns that interface. Should not be called before + // typechecking. + auto found_in_interface() const + -> std::optional> { + return found_in_interface_; + } + + // Can only be called once, during typechecking. + void set_found_in_interface(Nonnull interface) { + CARBON_CHECK(!found_in_interface_.has_value()); + found_in_interface_ = interface; + } + private: Nonnull object_; std::string member_; std::optional> impl_; + std::optional> found_in_interface_; }; // A compound member access expression of the form `object.(path)`. diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index b4fa2aa69b6c9..a4be663595dcd 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -808,11 +808,13 @@ auto Interpreter::StepExp() -> ErrorOr { } case ExpressionKind::SimpleMemberAccessExpression: { const auto& access = cast(exp); + bool forming_member_name = isa(&access.static_type()); if (act.pos() == 0) { // First, evaluate the first operand. return todo_.Spawn( std::make_unique(&access.object())); - } else if (act.pos() == 1 && access.impl().has_value()) { + } else if (act.pos() == 1 && access.impl().has_value() && + !forming_member_name) { // Next, if we're accessing an interface member, evaluate the `impl` // expression to find the corresponding witness. return todo_.Spawn( @@ -828,20 +830,14 @@ auto Interpreter::StepExp() -> ErrorOr { std::optional iface_result; std::optional type_result; if (auto* iface_type = dyn_cast(act.results()[0])) { + // This is `Interface.Member`. iface_result = iface_type; } else { + // This is `Type.Member` or `TypeParameter.Member`. In the latter + // case, we know both the type and the interface in which the + // member is found. type_result = act.results()[0]; - if (access.impl().has_value()) { - // TODO: A Witness should know the interface type it's a witness - // for. - // FIXME: A lot still to do here. - Nonnull witness = cast(act.results()[1]); - Nonnull iface = - witness->declaration() - .interface_type(); // better hope it's not parameterized - iface_result = cast( - iface); // better hope this isn't a constraint - } + iface_result = access.found_in_interface(); } MemberName* member_name = arena_->New( type_result, iface_result, member_name_type->member()); diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 5bf197d1105af..f86aee817f761 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -1469,6 +1469,7 @@ auto TypeChecker::TypeCheckExp(Nonnull e, CARBON_CHECK(var_type.binding().impl_binding().has_value()); access.set_impl( CreateImplReference(*var_type.binding().impl_binding())); + access.set_found_in_interface(&iface_type); switch ((*member)->kind()) { case DeclarationKind::FunctionDeclaration: { From 559e4a5b6f2ec28e1c78475b7491bab205da3180 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 1 Jun 2022 14:14:47 -0700 Subject: [PATCH 06/11] Add tests for new errors. --- .../constraint/ambiguous_member.carbon | 29 +++++++++++++++++++ .../testdata/constraint/missing_member.carbon | 29 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 explorer/testdata/constraint/ambiguous_member.carbon create mode 100644 explorer/testdata/constraint/missing_member.carbon diff --git a/explorer/testdata/constraint/ambiguous_member.carbon b/explorer/testdata/constraint/ambiguous_member.carbon new file mode 100644 index 0000000000000..f954900111c0a --- /dev/null +++ b/explorer/testdata/constraint/ambiguous_member.carbon @@ -0,0 +1,29 @@ +// 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} %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +interface A { fn F() -> i32; } +interface B { fn F() -> i32; } + +// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/ambiguous_member.carbon:[[@LINE+1]]: ambiguous member access, F found in interface A and interface B +fn Get[T:! A & B](n: T) -> i32 { return n.F(); } + +impl i32 as A { + fn F() -> i32 { return 1; } +} +impl i32 as B { + fn F() -> i32 { return 2; } +} + +fn Main() -> i32 { + var z: i32 = 0; + return Get(z); +} diff --git a/explorer/testdata/constraint/missing_member.carbon b/explorer/testdata/constraint/missing_member.carbon new file mode 100644 index 0000000000000..aeffa6877ad28 --- /dev/null +++ b/explorer/testdata/constraint/missing_member.carbon @@ -0,0 +1,29 @@ +// 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} %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +interface A { fn F() -> i32; } +interface B { fn G() -> i32; } + +// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/missing_member.carbon:[[@LINE+1]]: member access, H not in constraint interface A & interface B where .Self:! Type is interface A, .Self:! Type is interface B +fn Get[T:! A & B](n: T) -> i32 { return n.H(); } + +impl i32 as A { + fn F() -> i32 { return 1; } +} +impl i32 as B { + fn G() -> i32 { return 2; } +} + +fn Main() -> i32 { + var z: i32 = 0; + return Get(z); +} From e7c0fbb4775c8edcc31204440a8f05bb8e08847a Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 1 Jun 2022 15:28:12 -0700 Subject: [PATCH 07/11] Expand support for constraint member access to cover the various different syntactic forms. --- explorer/interpreter/interpreter.cpp | 15 +- explorer/interpreter/type_checker.cpp | 280 +++++++++--------- .../fail_field_access_on_generic.carbon | 2 +- .../fail_interface_missing_member.carbon | 2 +- .../qualified_constraint_member.carbon | 33 +++ 5 files changed, 178 insertions(+), 154 deletions(-) create mode 100644 explorer/testdata/member_access/qualified_constraint_member.carbon diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index a4be663595dcd..d794611d2854f 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -827,20 +827,13 @@ auto Interpreter::StepExp() -> ErrorOr { // suitable member name value. CARBON_CHECK(phase() == Phase::CompileTime) << "should not form MemberNames at runtime"; - std::optional iface_result; std::optional type_result; - if (auto* iface_type = dyn_cast(act.results()[0])) { - // This is `Interface.Member`. - iface_result = iface_type; - } else { - // This is `Type.Member` or `TypeParameter.Member`. In the latter - // case, we know both the type and the interface in which the - // member is found. + if (!isa(act.results()[0])) { type_result = act.results()[0]; - iface_result = access.found_in_interface(); } - MemberName* member_name = arena_->New( - type_result, iface_result, member_name_type->member()); + MemberName* member_name = + arena_->New(type_result, access.found_in_interface(), + member_name_type->member()); return todo_.FinishAction(member_name); } else { // The result is the value of the named field, such as in diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index f86aee817f761..9217bbc6f9b1c 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -356,15 +356,18 @@ auto TypeChecker::IsImplicitlyConvertible( case Value::Kind::TypeType: // FIXME: This seems suspicious. Shouldn't this require that the type // implements the interface? - if (destination->kind() == Value::Kind::InterfaceType) { + if (isa(destination)) { return true; } break; case Value::Kind::InterfaceType: + case Value::Kind::ConstraintType: case Value::Kind::TypeOfClassType: case Value::Kind::TypeOfChoiceType: - // FIXME: These types should presumably also convert to interface types. - if (destination->kind() == Value::Kind::TypeType) { + case Value::Kind::TypeOfInterfaceType: + case Value::Kind::TypeOfConstraintType: + // FIXME: These types should presumably also convert to constraint types. + if (isa(destination)) { return true; } break; @@ -1128,6 +1131,69 @@ auto TypeChecker::DeduceCallBindings( return Success(); } +struct ConstraintLookupResult { + Nonnull interface; + Nonnull member; + Nonnull impl; +}; + +/// Look up a member name in a constraint, which might be a single interface or +/// a compound constraint. +static auto LookupInConstraint(SourceLocation source_loc, + Nonnull type, + const std::string& member_name) + -> ErrorOr { + // Find the set of lookup contexts. + llvm::ArrayRef lookup_contexts; + ConstraintType::LookupContext interface_context[1]; + if (const auto* iface_type = dyn_cast(type)) { + // For an interface, look into that interface alone. + // TODO: Also look into any interfaces extended by it. + interface_context[0].context = iface_type; + lookup_contexts = interface_context; + } else if (const auto* constraint_type = dyn_cast(type)) { + // For a constraint, look in all of its lookup contexts. + lookup_contexts = constraint_type->lookup_contexts(); + } else { + // Other kinds of constraint, such as TypeType, have no lookup contexts. + } + + std::optional found; + for (ConstraintType::LookupContext lookup : lookup_contexts) { + if (!isa(lookup.context)) { + // TODO: Support other kinds of lookup context, notably named + // constraints. + continue; + } + const InterfaceType& iface_type = cast(*lookup.context); + if (std::optional> member = + FindMember(member_name, iface_type.declaration().members()); + member.has_value()) { + if (found.has_value()) { + if (ValueEqual(found->interface, &iface_type)) { + continue; + } + // TODO: If we resolve to the same member either way, this + // is not ambiguous. + return CompilationError(source_loc) + << "ambiguous member access, " << member_name << " found in " + << *found->interface << " and " << iface_type; + } + found = {.interface = &iface_type, .member = member.value()}; + } + } + + if (!found) { + if (isa(type)) { + return CompilationError(source_loc) + << "member access into unconstrained type"; + } + return CompilationError(source_loc) + << "member access, " << member_name << " not in " << *type; + } + return found.value(); +} + auto TypeChecker::TypeCheckExp(Nonnull e, const ImplScope& impl_scope) -> ErrorOr { @@ -1346,162 +1412,94 @@ auto TypeChecker::TypeCheckExp(Nonnull e, << access.member(); } } - case Value::Kind::TypeOfInterfaceType: { - const InterfaceType& iface_type = - cast(object_type).interface_type(); - if (std::optional> member = FindMember( - access.member(), iface_type.declaration().members()); - member.has_value()) { - access.set_static_type( - arena_->New(Member(*member))); - access.set_value_category(ValueCategory::Let); - return Success(); + case Value::Kind::TypeOfInterfaceType: + case Value::Kind::TypeOfConstraintType: { + const Value* type; + if (isa(object_type)) { + type = &cast(object_type).interface_type(); } else { - return CompilationError(access.source_loc()) - << iface_type << " does not have a member named " - << access.member(); + type = &cast(object_type).constraint_type(); } + CARBON_ASSIGN_OR_RETURN( + ConstraintLookupResult result, + LookupInConstraint(e->source_loc(), type, access.member())); + access.set_found_in_interface(result.interface); + access.set_static_type( + arena_->New(Member(result.member))); + access.set_value_category(ValueCategory::Let); + return Success(); } case Value::Kind::VariableType: { // This case handles access to a method on a receiver whose type // is a type variable. For example, `x.foo` where the type of // `x` is `T` and `foo` and `T` implements an interface that // includes `foo`. - const VariableType& var_type = cast(object_type); - const Value& typeof_var = var_type.binding().static_type(); - switch (typeof_var.kind()) { - case Value::Kind::InterfaceType: { - const auto& iface_type = cast(typeof_var); - const InterfaceDeclaration& iface_decl = iface_type.declaration(); - if (std::optional> member = - FindMember(access.member(), 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(binding_map, &member_type); - access.set_static_type(inst_member_type); - CARBON_CHECK(var_type.binding().impl_binding().has_value()); - access.set_impl( - CreateImplReference(*var_type.binding().impl_binding())); - return Success(); - } else { - return CompilationError(e->source_loc()) - << "member access, " << access.member() << " not in " - << iface_decl.name(); - } - break; - } - case Value::Kind::ConstraintType: { - const auto& constraint = cast(typeof_var); - std::optional> found_in; - for (ConstraintType::LookupContext ctx : - constraint.lookup_contexts()) { - BindingMap constraint_self_map; - constraint_self_map[constraint.self_binding()] = &var_type; - Nonnull resolved_context = - Substitute(constraint_self_map, ctx.context); - if (!isa(resolved_context)) { - // TODO: Support other kinds of lookup context, notably named - // constraints. - continue; - } - const auto& iface_type = cast(*resolved_context); - const InterfaceDeclaration& iface_decl = - iface_type.declaration(); - if (std::optional> member = - FindMember(access.member(), iface_decl.members()); - member.has_value()) { - if (found_in.has_value() && - !ValueEqual(found_in.value(), resolved_context)) { - // TODO: If we resolve to the same member either way, this - // is not ambiguous. - return CompilationError(e->source_loc()) - << "ambiguous member access, " << access.member() - << " found in " << *found_in.value() << " and " - << *resolved_context; - } - 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(binding_map, &member_type); - access.set_static_type(inst_member_type); - // TODO: We could dig this out of the witness for the impl - // binding; it should always be there. - CARBON_ASSIGN_OR_RETURN( - Nonnull impl, - impl_scope.Resolve(&iface_type, &var_type, - e->source_loc(), *this)); - access.set_impl(impl); - found_in = resolved_context; - } - } - if (!found_in) { - return CompilationError(e->source_loc()) - << "member access, " << access.member() << " not in " - << constraint; - } - return Success(); - } - default: - return CompilationError(e->source_loc()) - << "member access, unexpected " << object_type - << " of non-interface type " << typeof_var << " in " << *e; - } - break; + const Value& typeof_var = + cast(object_type).binding().static_type(); + CARBON_ASSIGN_OR_RETURN( + ConstraintLookupResult result, + LookupInConstraint(e->source_loc(), &typeof_var, + access.member())); + + const Value& member_type = result.member->static_type(); + BindingMap binding_map = result.interface->args(); + binding_map[result.interface->declaration().self()] = &object_type; + Nonnull inst_member_type = + Substitute(binding_map, &member_type); + access.set_found_in_interface(result.interface); + access.set_static_type(inst_member_type); + + CARBON_ASSIGN_OR_RETURN( + Nonnull impl, + impl_scope.Resolve(result.interface, &object_type, + e->source_loc(), *this)); + access.set_impl(impl); + return Success(); } - case Value::Kind::InterfaceType: { + case Value::Kind::InterfaceType: + case Value::Kind::ConstraintType: { // This case handles access to a class function from a type variable. // If `T` is a type variable and `foo` is a class function in an // interface implemented by `T`, then `T.foo` accesses the `foo` class // function of `T`. CARBON_ASSIGN_OR_RETURN( - Nonnull var_addr, + Nonnull type, InterpExp(&access.object(), arena_, trace_stream_)); - const VariableType& var_type = cast(*var_addr); - const InterfaceType& iface_type = cast(object_type); - const InterfaceDeclaration& iface_decl = iface_type.declaration(); - if (std::optional> member = - FindMember(access.member(), iface_decl.members()); - member.has_value()) { - CARBON_CHECK(var_type.binding().impl_binding().has_value()); - access.set_impl( - CreateImplReference(*var_type.binding().impl_binding())); - access.set_found_in_interface(&iface_type); - - switch ((*member)->kind()) { - case DeclarationKind::FunctionDeclaration: { - const auto& func = cast(*member); - if (func->is_method()) { - break; - } - 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(binding_map, &member_type); - access.set_static_type(inst_member_type); - return Success(); - } - default: + CARBON_ASSIGN_OR_RETURN( + ConstraintLookupResult result, + LookupInConstraint(e->source_loc(), &object_type, + access.member())); + CARBON_ASSIGN_OR_RETURN(Nonnull impl, + impl_scope.Resolve(result.interface, type, + e->source_loc(), *this)); + access.set_impl(impl); + access.set_found_in_interface(result.interface); + + switch (result.member->kind()) { + case DeclarationKind::FunctionDeclaration: { + const auto& func = cast(*result.member); + if (func.is_method()) { break; + } + const Value& member_type = func.static_type(); + BindingMap binding_map = result.interface->args(); + binding_map[result.interface->declaration().self()] = type; + Nonnull inst_member_type = + Substitute(binding_map, &member_type); + access.set_static_type(inst_member_type); + return Success(); } - // TODO: Consider setting the static type of all interface member - // declarations and instance member declarations to be member name - // types, rather than special-casing member accesses that name - // them. - access.set_static_type( - arena_->New(Member(*member))); - access.set_value_category(ValueCategory::Let); - return Success(); - } else { - return CompilationError(e->source_loc()) - << "member access, " << access.member() << " not in " - << iface_decl.name(); + default: + break; } - break; + // TODO: Consider setting the static type of all interface member + // declarations and instance member declarations to be member name + // types, rather than special-casing member accesses that name + // them. + access.set_static_type( + arena_->New(Member(result.member))); + access.set_value_category(ValueCategory::Let); + return Success(); } default: return CompilationError(e->source_loc()) diff --git a/explorer/testdata/generic_class/fail_field_access_on_generic.carbon b/explorer/testdata/generic_class/fail_field_access_on_generic.carbon index b6a1db1e97b0c..e36cc590ba930 100644 --- a/explorer/testdata/generic_class/fail_field_access_on_generic.carbon +++ b/explorer/testdata/generic_class/fail_field_access_on_generic.carbon @@ -11,7 +11,7 @@ package ExplorerTest api; fn BadSimpleMemberAccess[T:! Type](a: T) -> T { - // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_class/fail_field_access_on_generic.carbon:[[@LINE+1]]: member access, unexpected T:! Type of non-interface type Type in a.x + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_class/fail_field_access_on_generic.carbon:[[@LINE+1]]: member access into unconstrained type return a.x; } diff --git a/explorer/testdata/interface/fail_interface_missing_member.carbon b/explorer/testdata/interface/fail_interface_missing_member.carbon index 236bd79a3e3fa..14bbe7fb0a469 100644 --- a/explorer/testdata/interface/fail_interface_missing_member.carbon +++ b/explorer/testdata/interface/fail_interface_missing_member.carbon @@ -15,7 +15,7 @@ interface Vector { } fn ScaleGeneric[T:! Vector](a: T, s: i32) -> T { - // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_interface_missing_member.carbon:[[@LINE+1]]: member access, Scale not in Vector + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_interface_missing_member.carbon:[[@LINE+1]]: member access, Scale not in interface Vector return a.Scale(s); } diff --git a/explorer/testdata/member_access/qualified_constraint_member.carbon b/explorer/testdata/member_access/qualified_constraint_member.carbon new file mode 100644 index 0000000000000..ed62b95a73987 --- /dev/null +++ b/explorer/testdata/member_access/qualified_constraint_member.carbon @@ -0,0 +1,33 @@ +// 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: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 12 + +package Foo api; +interface A { + fn F[me: Self]() -> i32; +} +interface B { + fn G(o: Self) -> i32; +} +alias C = A & B; +class X { + impl as A { + fn F[me: Self]() -> i32 { return 10 * me.n; } + } + impl as B { + fn G(o: Self) -> i32 { return o.n; } + } + var n: i32; +} +fn Main() -> i32 { + var v: X = {.n = 1}; + var w: X = {.n = 2}; + return v.(C.F)() + X.(C.G)(w); +} From be067c2777f24f9ab0f40867fb70912b332b3a28 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 1 Jun 2022 15:35:19 -0700 Subject: [PATCH 08/11] Rename SameTypeCostraint to EqualityConstraint since it can also apply to non-types. Change printing from using `=` to using `==` in line with discussion in weekly sync meeting. --- explorer/interpreter/type_checker.cpp | 60 ++++++++++++++------------- explorer/interpreter/value.cpp | 26 ++++++------ explorer/interpreter/value.h | 19 +++++---- 3 files changed, 54 insertions(+), 51 deletions(-) diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 9217bbc6f9b1c..5773e0e21dbaf 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -810,15 +810,15 @@ auto TypeChecker::Substitute( Substitute(dict, impl_constraint.interface))}); } - std::vector same_type_constraints; - same_type_constraints.reserve(constraint.same_type_constraints().size()); - for (const auto& same_type_constraint : - constraint.same_type_constraints()) { - std::vector> types; - for (const Value* type : same_type_constraint.types) { - types.push_back(Substitute(dict, type)); + std::vector equality_constraints; + equality_constraints.reserve(constraint.equality_constraints().size()); + for (const auto& equality_constraint : + constraint.equality_constraints()) { + std::vector> values; + for (const Value* value : equality_constraint.values) { + values.push_back(Substitute(dict, value)); } - same_type_constraints.push_back({.types = types}); + equality_constraints.push_back({.values = values}); } // TODO: Coalesce same-type constraints that are now overlapping. @@ -833,7 +833,7 @@ auto TypeChecker::Substitute( Nonnull new_constraint = arena_->New( constraint.self_binding(), std::move(impl_constraints), - std::move(same_type_constraints), std::move(lookup_contexts)); + std::move(equality_constraints), std::move(lookup_contexts)); if (trace_stream_) { **trace_stream_ << "substitution: " << constraint << " => " << *new_constraint << "\n"; @@ -990,11 +990,11 @@ auto TypeChecker::MakeConstraintForInterface( auto* self = arena_->New(self_binding); std::vector impl_constraints = { ConstraintType::ImplConstraint{.type = self, .interface = iface_type}}; - std::vector same_type_constraints = {}; + std::vector equality_constraints = {}; std::vector lookup_contexts = { {.context = iface_type}}; return arena_->New(self_binding, std::move(impl_constraints), - std::move(same_type_constraints), + std::move(equality_constraints), std::move(lookup_contexts)); } @@ -1006,7 +1006,7 @@ auto TypeChecker::CombineConstraints( source_loc, ".Self", arena_->New(source_loc)); auto* self = arena_->New(self_binding); std::vector impl_constraints; - std::vector same_type_constraints; + std::vector equality_constraints; std::vector lookup_contexts; for (Nonnull constraint : constraints) { BindingMap map; @@ -1017,32 +1017,34 @@ auto TypeChecker::CombineConstraints( {.type = Substitute(map, impl.type), .interface = cast(Substitute(map, impl.interface))}); } - for (ConstraintType::SameTypeConstraint same : - constraint->same_type_constraints()) { - std::vector> types; - for (const Value* type : same.types) { - types.push_back(Substitute(map, type)); + for (ConstraintType::EqualityConstraint same : + constraint->equality_constraints()) { + std::vector> values; + for (const Value* value : same.values) { + values.push_back(Substitute(map, value)); } - auto AddSameTypeConstraint = - [&](std::vector> types) { + auto AddEqualityConstraint = + [&](std::vector> values) { // TODO: This is really inefficient. Use value canonicalization or // hashing or similar to avoid the quadratic scan here. - for (const Value* type : types) { - for (ConstraintType::SameTypeConstraint& existing : - same_type_constraints) { - for (const Value* existing_type : existing.types) { - if (TypeEqual(type, existing_type)) { + for (const Value* value : values) { + for (ConstraintType::EqualityConstraint& existing : + equality_constraints) { + for (const Value* existing_value : existing.values) { + if (ValueEqual(value, existing_value)) { + // There is overlap between two equality constraints. + // Combine them into a single constraint. // TODO: Remove duplicates - existing.types.insert(existing.types.end(), types.begin(), - types.end()); + existing.values.insert(existing.values.end(), + values.begin(), values.end()); return; } } } } - same_type_constraints.push_back({.types = std::move(types)}); + equality_constraints.push_back({.values = std::move(values)}); }; - AddSameTypeConstraint(std::move(types)); + AddEqualityConstraint(std::move(values)); } // TODO: Remove duplicates for (ConstraintType::LookupContext lookup : constraint->lookup_contexts()) { @@ -1050,7 +1052,7 @@ auto TypeChecker::CombineConstraints( } } return arena_->New(self_binding, std::move(impl_constraints), - std::move(same_type_constraints), + std::move(equality_constraints), std::move(lookup_contexts)); } diff --git a/explorer/interpreter/value.cpp b/explorer/interpreter/value.cpp index b66bb41e6ed3c..1c7ae91d1a552 100644 --- a/explorer/interpreter/value.cpp +++ b/explorer/interpreter/value.cpp @@ -369,12 +369,12 @@ void Value::Print(llvm::raw_ostream& out) const { // in `lookup_contexts()`. out << sep << *impl.type << " is " << *impl.interface; } - for (const ConstraintType::SameTypeConstraint& same_type : - constraint.same_type_constraints()) { + for (const ConstraintType::EqualityConstraint& equality : + constraint.equality_constraints()) { out << sep; - llvm::ListSeparator equal(" = "); - for (Nonnull type : same_type.types) { - out << equal << *type; + llvm::ListSeparator equal(" == "); + for (Nonnull value : equality.values) { + out << equal << *value; } } break; @@ -556,8 +556,8 @@ auto TypeEqual(Nonnull t1, Nonnull t2) -> bool { const auto& constraint2 = cast(*t2); if (constraint1.impl_constraints().size() != constraint2.impl_constraints().size() || - constraint1.same_type_constraints().size() != - constraint2.same_type_constraints().size() || + constraint1.equality_constraints().size() != + constraint2.equality_constraints().size() || constraint1.lookup_contexts().size() != constraint2.lookup_contexts().size()) { return false; @@ -570,14 +570,14 @@ auto TypeEqual(Nonnull t1, Nonnull t2) -> bool { return false; } } - for (size_t i = 0; i < constraint1.same_type_constraints().size(); ++i) { - const auto& same_type1 = constraint1.same_type_constraints()[i]; - const auto& same_type2 = constraint2.same_type_constraints()[i]; - if (same_type1.types.size() != same_type2.types.size()) { + for (size_t i = 0; i < constraint1.equality_constraints().size(); ++i) { + const auto& equality1 = constraint1.equality_constraints()[i]; + const auto& equality2 = constraint2.equality_constraints()[i]; + if (equality1.values.size() != equality2.values.size()) { return false; } - for (size_t j = 0; j < same_type1.types.size(); ++j) { - if (!TypeEqual(same_type1.types[i], same_type2.types[i])) { + for (size_t j = 0; j < equality1.values.size(); ++j) { + if (!ValueEqual(equality1.values[i], equality2.values[i])) { return false; } } diff --git a/explorer/interpreter/value.h b/explorer/interpreter/value.h index 343f35b33e03a..f4f8bc4b1bedb 100644 --- a/explorer/interpreter/value.h +++ b/explorer/interpreter/value.h @@ -658,7 +658,8 @@ class InterfaceType : public Value { // // * A collection of (type, interface) pairs for interfaces that are known to // be implemented by a type satisfying the constraint. -// * A collection of sets of types that are known to be the same. +// * A collection of sets of values, typically associated constants, that are +// known to be the same. // * A collection of contexts in which member name lookups will be performed // for a type variable whose type is this constraint. // @@ -672,9 +673,9 @@ class ConstraintType : public Value { Nonnull interface; }; - // A collection of types that are known to be the same. - struct SameTypeConstraint { - std::vector> types; + // A collection of values that are known to be the same. + struct EqualityConstraint { + std::vector> values; }; // A context in which we might look up a name. @@ -685,12 +686,12 @@ class ConstraintType : public Value { public: explicit ConstraintType(Nonnull self_binding, std::vector impl_constraints, - std::vector same_type_constraints, + std::vector equality_constraints, std::vector lookup_contexts) : Value(Kind::ConstraintType), self_binding_(self_binding), impl_constraints_(std::move(impl_constraints)), - same_type_constraints_(std::move(same_type_constraints)), + equality_constraints_(std::move(equality_constraints)), lookup_contexts_(std::move(lookup_contexts)) {} static auto classof(const Value* value) -> bool { @@ -705,8 +706,8 @@ class ConstraintType : public Value { return impl_constraints_; } - auto same_type_constraints() const -> llvm::ArrayRef { - return same_type_constraints_; + auto equality_constraints() const -> llvm::ArrayRef { + return equality_constraints_; } auto lookup_contexts() const -> llvm::ArrayRef { @@ -716,7 +717,7 @@ class ConstraintType : public Value { private: Nonnull self_binding_; std::vector impl_constraints_; - std::vector same_type_constraints_; + std::vector equality_constraints_; std::vector lookup_contexts_; }; From ad122131ddff5f46b6220dd8991db34273443620 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Thu, 2 Jun 2022 13:24:19 -0700 Subject: [PATCH 09/11] Fuzzing support for & operator. --- common/fuzzing/carbon.proto | 1 + common/fuzzing/proto_to_carbon.cpp | 4 ++++ explorer/fuzzing/ast_to_proto.cpp | 2 ++ 3 files changed, 7 insertions(+) diff --git a/common/fuzzing/carbon.proto b/common/fuzzing/carbon.proto index bb4b72a494a29..8aa70bdbb9712 100644 --- a/common/fuzzing/carbon.proto +++ b/common/fuzzing/carbon.proto @@ -52,6 +52,7 @@ message PrimitiveOperatorExpression { Or = 9; Sub = 10; Ptr = 11; + Combine = 12; } optional Operator op = 1; repeated Expression arguments = 2; diff --git a/common/fuzzing/proto_to_carbon.cpp b/common/fuzzing/proto_to_carbon.cpp index 9234fd9fa3df8..abecd96ed6b9b 100644 --- a/common/fuzzing/proto_to_carbon.cpp +++ b/common/fuzzing/proto_to_carbon.cpp @@ -146,6 +146,10 @@ static auto PrimitiveOperatorToCarbon( case Fuzzing::PrimitiveOperatorExpression::Or: BinaryOperatorToCarbon(arg0, " or ", arg1, out); break; + + case Fuzzing::PrimitiveOperatorExpression::Combine: + BinaryOperatorToCarbon(arg0, " & ", arg1, out); + break; } out << ")"; } diff --git a/explorer/fuzzing/ast_to_proto.cpp b/explorer/fuzzing/ast_to_proto.cpp index 8e6dd45acee99..91870654cc659 100644 --- a/explorer/fuzzing/ast_to_proto.cpp +++ b/explorer/fuzzing/ast_to_proto.cpp @@ -56,6 +56,8 @@ static auto OperatorToProtoEnum(const Operator op) return Fuzzing::PrimitiveOperatorExpression::Or; case Operator::Sub: return Fuzzing::PrimitiveOperatorExpression::Sub; + case Operator::Combine: + return Fuzzing::PrimitiveOperatorExpression::Combine; } } From 51e864c8c4a85520fc39cb5585e2c8de03059d4c Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 7 Jun 2022 13:18:14 -0700 Subject: [PATCH 10/11] Address review comments. --- explorer/interpreter/value.h | 3 +++ explorer/testdata/constraint/combined_interfaces.carbon | 6 +++--- ...ambiguous_member.carbon => fail_ambiguous_member.carbon} | 0 3 files changed, 6 insertions(+), 3 deletions(-) rename explorer/testdata/constraint/{ambiguous_member.carbon => fail_ambiguous_member.carbon} (100%) diff --git a/explorer/interpreter/value.h b/explorer/interpreter/value.h index 7d45c89d22b8c..05a07d78dda33 100644 --- a/explorer/interpreter/value.h +++ b/explorer/interpreter/value.h @@ -674,6 +674,9 @@ class InterfaceType : public Value { // A type-of-type for an unknown constrained type. // +// These types are formed by the `&` operator that combines constraints and by +// `where` expressions. +// // A constraint has three main properties: // // * A collection of (type, interface) pairs for interfaces that are known to diff --git a/explorer/testdata/constraint/combined_interfaces.carbon b/explorer/testdata/constraint/combined_interfaces.carbon index 22982a6b7aa0e..c336854b33dc9 100644 --- a/explorer/testdata/constraint/combined_interfaces.carbon +++ b/explorer/testdata/constraint/combined_interfaces.carbon @@ -7,14 +7,14 @@ // RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ // RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s // AUTOUPDATE: %{explorer} %s -// CHECK: result: 3 +// CHECK: result: 52 package ExplorerTest api; interface A { fn F() -> i32; fn G() -> i32; } interface B { fn H() -> i32; } -fn Get1[T:! A & B](n: T) -> i32 { return n.F(); } +fn Get1[T:! A & B](n: T) -> i32 { return n.F() + n.H(); } fn Get2[T:! B & A](n: T) -> i32 { return n.G(); } impl i32 as A { @@ -27,5 +27,5 @@ impl i32 as B { fn Main() -> i32 { var z: i32 = 0; - return Get1(z) + Get2(z); + return Get1(z) * 10 + Get2(z); } diff --git a/explorer/testdata/constraint/ambiguous_member.carbon b/explorer/testdata/constraint/fail_ambiguous_member.carbon similarity index 100% rename from explorer/testdata/constraint/ambiguous_member.carbon rename to explorer/testdata/constraint/fail_ambiguous_member.carbon From 8a0126404d61efe3409c9e452a15f68552f6f2d3 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 7 Jun 2022 13:19:58 -0700 Subject: [PATCH 11/11] Fix test expectation after rename. --- explorer/testdata/constraint/fail_ambiguous_member.carbon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/explorer/testdata/constraint/fail_ambiguous_member.carbon b/explorer/testdata/constraint/fail_ambiguous_member.carbon index f954900111c0a..33ecde91c2ff7 100644 --- a/explorer/testdata/constraint/fail_ambiguous_member.carbon +++ b/explorer/testdata/constraint/fail_ambiguous_member.carbon @@ -13,7 +13,7 @@ package ExplorerTest api; interface A { fn F() -> i32; } interface B { fn F() -> i32; } -// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/ambiguous_member.carbon:[[@LINE+1]]: ambiguous member access, F found in interface A and interface B +// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/fail_ambiguous_member.carbon:[[@LINE+1]]: ambiguous member access, F found in interface A and interface B fn Get[T:! A & B](n: T) -> i32 { return n.F(); } impl i32 as A {