// 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 #ifndef CARBON_EXPLORER_INTERPRETER_IMPL_SCOPE_H_ #define CARBON_EXPLORER_INTERPRETER_IMPL_SCOPE_H_ #include "explorer/ast/declaration.h" #include "explorer/ast/value.h" #include "explorer/interpreter/type_structure.h" namespace Carbon { class TypeChecker; // The `ImplScope` class is responsible for mapping a type and // interface to the location of the witness table for the `impl` for // that type and interface. A scope may have parent scopes, whose // implementations will also be visible in the child scope. // // There is typically one instance of `ImplScope` class per scope // because the implementationss that are visible for a given type and // interface can vary from scope to scope. For example, consider the // `bar` and `baz` methods in the following class C and nested class D. // // class C(U:! type, T:! type) { // class D(V:! type where U impls Fooable(T)) { // fn bar[self: Self](x: U, y : T) -> T{ // return x.foo(y) // } // } // fn baz[self: Self](x: U, y : T) -> T { // return x.foo(y); // } // } // // The call to `x.foo` in `bar` is valid because the `U is Fooable(T)` // impl is visible in the body of `bar`. In contrast, the call to // `x.foo` in `baz` is not valid because there is no visible impl for // `U` and `Fooable` in that scope. // // `ImplScope` also tracks the type equalities that are known in a particular // scope. class ImplScope : public Printable { public: // The `ImplFact` struct is a key-value pair where the key is the // combination of a type and an interface, e.g., `List` and `Container`, // and the value is the result of statically resolving to the `impl` // for `List` as `Container`, which is an `Expression` that produces // the witness for that `impl`. // // When the `impl` is parameterized, `deduced` and `impl_bindings` // are non-empty. The former contains the type parameters and the // later are impl bindings, that is, parameters for witnesses. In this case, // `sort_key` indicates the order in which this impl should be considered // relative to other matching impls. struct ImplFact { Nonnull interface; std::vector> deduced; Nonnull type; std::vector> impl_bindings; Nonnull witness; std::optional sort_key; }; // Internal type used to represent the result of resolving a lookup in a // particular impl scope. struct ResolveResult { Nonnull impl; Nonnull witness; }; explicit ImplScope() {} explicit ImplScope(Nonnull parent) : parent_scope_(parent) {} // Associates `iface` and `type` with the `impl` in this scope. If `iface` is // a constraint type, it will be split into its constituent components, and // any references to `.Self` are expected to have been substituted for the // type implementing the constraint. void Add(Nonnull iface, Nonnull type, Nonnull witness, const TypeChecker& type_checker); // For a parameterized impl, associates `iface` and `type` // with the `impl` in this scope. Otherwise, the same as the previous // overload. void Add(Nonnull iface, llvm::ArrayRef> deduced, Nonnull type, llvm::ArrayRef> impl_bindings, Nonnull witness, const TypeChecker& type_checker, std::optional sort_key = std::nullopt); // Adds a list of impls constraints from a constraint type into scope. Any // references to `.Self` are expected to have already been substituted for // the type implementing the constraint. void Add(llvm::ArrayRef impls_constraints, llvm::ArrayRef> deduced, llvm::ArrayRef> impl_bindings, Nonnull witness, const TypeChecker& type_checker); // Adds a type equality constraint. void AddEqualityConstraint(Nonnull equal) { equalities_.push_back(equal); } // 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. // // If any substitutions should be made into the constraint before resolving // it, those should be passed in `bindings`. The witness returned will be for // `constraint`, not for the result of substituting the bindings into the // constraint. The substituted type might in general have a different shape // of witness due to deduplication. auto Resolve(Nonnull constraint, Nonnull type, SourceLocation source_loc, const TypeChecker& type_checker, const Bindings& bindings = {}) const -> ErrorOr>; // Same as Resolve, except that failure due to a missing implementation of a // constraint produces `nullopt` instead of an error if // `diagnose_missing_impl` is `false`. This is intended for cases where we're // selecting between options based on whether constraints are satisfied, such // as during `impl` selection. auto TryResolve(Nonnull constraint, Nonnull type, SourceLocation source_loc, const TypeChecker& type_checker, const Bindings& bindings, bool diagnose_missing_impl) const -> ErrorOr>>; // Visits the values that are a single step away from `value` according to an // equality constraint that is in scope. That is, the values `v` such that we // have a `value == v` equality constraint in scope. // // Stops and returns `false` if any call to the visitor returns `false`, // otherwise returns `true`. auto VisitEqualValues( Nonnull value, llvm::function_ref)> visitor) const -> bool; void Print(llvm::raw_ostream& out) const; private: // Returns the associated impl for the given `iface` and `type` in // the ancestor graph of this scope. Reports a compilation error // at `source_loc` if there's an ambiguity, or if `diagnose_missing_impl` is // set and there's no matching impl. auto TryResolveInterface(Nonnull iface, Nonnull type, SourceLocation source_loc, const TypeChecker& type_checker, bool diagnose_missing_impl) 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 TryResolveInterfaceRecursively(Nonnull iface_type, Nonnull type, SourceLocation source_loc, const ImplScope& original_scope, const TypeChecker& type_checker) const -> ErrorOr>; // Returns the associated impl for the given `iface` and `type` in // 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 TryResolveInterfaceHere(Nonnull iface_type, Nonnull impl_type, SourceLocation source_loc, const ImplScope& original_scope, const TypeChecker& type_checker) const -> ErrorOr>; std::vector impl_facts_; std::vector> equalities_; std::optional> parent_scope_; }; // An equality context that considers two values to be equal if they are a // single step apart according to an equality constraint in the given impl // scope. struct SingleStepEqualityContext : public EqualityContext { public: explicit SingleStepEqualityContext(Nonnull impl_scope) : impl_scope_(impl_scope) {} // Visits the values that are equal to the given value and a single step away // according to an equality constraint that is in the given impl scope. Stops // and returns `false` if the visitor returns `false`, otherwise returns // `true`. auto VisitEqualValues(Nonnull value, llvm::function_ref)> visitor) const -> bool override; private: Nonnull impl_scope_; }; } // namespace Carbon #endif // CARBON_EXPLORER_INTERPRETER_IMPL_SCOPE_H_