blob: 18a65fec0483d3c0a79ca6f5d76f9e891554bd7e [file] [log] [blame]
// Copyright (C) 2020-2025 Free Software Foundation, Inc.
// This file is part of GCC.
// GCC is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3, or (at your option) any later
// version.
// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
// for more details.
// You should have received a copy of the GNU General Public License
// along with GCC; see the file COPYING3. If not see
// <http://www.gnu.org/licenses/>.
#ifndef RUST_HIR_TYPE_CHECK
#define RUST_HIR_TYPE_CHECK
#include "rust-hir-map.h"
#include "rust-tyty.h"
#include "rust-hir-trait-reference.h"
#include "rust-stacked-contexts.h"
#include "rust-autoderef.h"
#include "rust-tyty-region.h"
#include "rust-tyty-variance-analysis.h"
#include "rust-system.h"
namespace Rust {
namespace Resolver {
class TypeCheckContextItem
{
public:
enum ItemType
{
ITEM,
IMPL_ITEM,
TRAIT_ITEM,
ERROR
};
TypeCheckContextItem (HIR::Function *item);
TypeCheckContextItem (HIR::ImplBlock &impl_block, HIR::Function *item);
TypeCheckContextItem (HIR::TraitItemFunc *trait_item);
TypeCheckContextItem (const TypeCheckContextItem &other);
TypeCheckContextItem &operator= (const TypeCheckContextItem &other);
static TypeCheckContextItem get_error ();
bool is_error () const;
ItemType get_type () const;
HIR::Function *get_item ();
std::pair<HIR::ImplBlock *, HIR::Function *> &get_impl_item ();
HIR::TraitItemFunc *get_trait_item ();
TyTy::FnType *get_context_type ();
DefId get_defid () const;
private:
TypeCheckContextItem ();
union Item
{
HIR::Function *item;
std::pair<HIR::ImplBlock *, HIR::Function *> impl_item;
HIR::TraitItemFunc *trait_item;
Item (HIR::Function *item);
Item (HIR::ImplBlock *impl_block, HIR::Function *item);
Item (HIR::TraitItemFunc *trait_item);
};
ItemType type;
Item item;
};
class TypeCheckBlockContextItem
{
public:
enum ItemType
{
IMPL_BLOCK,
TRAIT
};
TypeCheckBlockContextItem (HIR::ImplBlock *block);
TypeCheckBlockContextItem (HIR::Trait *trait);
bool is_impl_block () const;
bool is_trait_block () const;
HIR::ImplBlock &get_impl_block ();
HIR::Trait &get_trait ();
private:
union Item
{
HIR::ImplBlock *block;
HIR::Trait *trait;
Item (HIR::ImplBlock *block);
Item (HIR::Trait *trait);
};
ItemType type;
Item item;
};
/**
* Interned lifetime representation in TyTy
*
* On the HIR->TyTy boundary HIR::Lifetime is interned into this struct.
*/
class Lifetime
{
uint32_t interner_index;
public:
explicit constexpr Lifetime (uint32_t interner_index)
: interner_index (interner_index)
{}
Lifetime () = default;
WARN_UNUSED_RESULT bool is_static () const { return interner_index == 0; }
WARN_UNUSED_RESULT static constexpr Lifetime static_lifetime ()
{
return Lifetime (0);
}
WARN_UNUSED_RESULT static constexpr Lifetime anonymous_lifetime ()
{
return Lifetime (1);
}
static constexpr uint32_t FIRST_NAMED_LIFETIME = 2;
friend bool operator== (const Lifetime &lhs, const Lifetime &rhs)
{
return lhs.interner_index == rhs.interner_index;
}
friend bool operator!= (const Lifetime &lhs, const Lifetime &rhs)
{
return !(lhs == rhs);
}
WARN_UNUSED_RESULT Lifetime next () { return Lifetime (interner_index++); }
};
class TypeCheckContext
{
public:
static TypeCheckContext *get ();
~TypeCheckContext ();
bool lookup_builtin (NodeId id, TyTy::BaseType **type);
bool lookup_builtin (std::string name, TyTy::BaseType **type);
void insert_builtin (HirId id, NodeId ref, TyTy::BaseType *type);
const std::vector<std::unique_ptr<TyTy::BaseType>> &get_builtins () const;
void insert_type (const Analysis::NodeMapping &mappings,
TyTy::BaseType *type);
bool lookup_type (HirId id, TyTy::BaseType **type) const;
void clear_type (TyTy::BaseType *ty);
void insert_implicit_type (HirId id, TyTy::BaseType *type);
void insert_type_by_node_id (NodeId ref, HirId id);
bool lookup_type_by_node_id (NodeId ref, HirId *id);
bool have_function_context () const;
TyTy::BaseType *peek_return_type ();
TypeCheckContextItem peek_context ();
void push_return_type (TypeCheckContextItem item,
TyTy::BaseType *return_type);
void pop_return_type ();
StackedContexts<TypeCheckBlockContextItem> &block_context ();
void iterate (std::function<bool (HirId, TyTy::BaseType *)> cb);
bool have_loop_context () const;
void push_new_loop_context (HirId id, location_t locus);
void push_new_while_loop_context (HirId id);
TyTy::BaseType *peek_loop_context ();
TyTy::BaseType *pop_loop_context ();
void swap_head_loop_context (TyTy::BaseType *val);
void insert_trait_reference (DefId id, TraitReference &&ref);
bool lookup_trait_reference (DefId id, TraitReference **ref);
void insert_associated_trait_impl (HirId id,
AssociatedImplTrait &&associated);
bool lookup_associated_trait_impl (HirId id,
AssociatedImplTrait **associated);
void insert_associated_type_mapping (HirId id, HirId mapping);
void clear_associated_type_mapping (HirId id);
// lookup any associated type mappings, the out parameter of mapping is
// allowed to be nullptr which allows this interface to do a simple does exist
// check
bool lookup_associated_type_mapping (HirId id, HirId *mapping);
void insert_associated_impl_mapping (HirId trait_id,
const TyTy::BaseType *impl_type,
HirId impl_id);
bool lookup_associated_impl_mapping_for_self (HirId trait_id,
const TyTy::BaseType *self,
HirId *mapping);
void insert_autoderef_mappings (HirId id,
std::vector<Adjustment> &&adjustments);
bool lookup_autoderef_mappings (HirId id,
std::vector<Adjustment> **adjustments);
void insert_cast_autoderef_mappings (HirId id,
std::vector<Adjustment> &&adjustments);
bool lookup_cast_autoderef_mappings (HirId id,
std::vector<Adjustment> **adjustments);
void insert_variant_definition (HirId id, HirId variant);
bool lookup_variant_definition (HirId id, HirId *variant);
void insert_operator_overload (HirId id, TyTy::FnType *call_site);
bool lookup_operator_overload (HirId id, TyTy::FnType **call);
void insert_unconstrained_check_marker (HirId id, bool status);
bool have_checked_for_unconstrained (HirId id, bool *result);
void insert_resolved_predicate (HirId id, TyTy::TypeBoundPredicate predicate);
bool lookup_predicate (HirId id, TyTy::TypeBoundPredicate *result);
void insert_query (HirId id);
void query_completed (HirId id);
bool query_in_progress (HirId id) const;
void insert_trait_query (DefId id);
void trait_query_completed (DefId id);
bool trait_query_in_progress (DefId id) const;
Lifetime intern_lifetime (const HIR::Lifetime &name);
WARN_UNUSED_RESULT tl::optional<Lifetime>
lookup_lifetime (const HIR::Lifetime &lifetime) const;
WARN_UNUSED_RESULT tl::optional<TyTy::Region>
lookup_and_resolve_lifetime (const HIR::Lifetime &lifetime) const;
void intern_and_insert_lifetime (const HIR::Lifetime &lifetime);
WARN_UNUSED_RESULT std::vector<TyTy::Region>
regions_from_generic_args (const HIR::GenericArgs &args) const;
void compute_inference_variables (bool error);
TyTy::VarianceAnalysis::CrateCtx &get_variance_analysis_ctx ();
private:
TypeCheckContext ();
std::map<NodeId, HirId> node_id_refs;
std::map<HirId, TyTy::BaseType *> resolved;
std::vector<std::unique_ptr<TyTy::BaseType>> builtins;
std::vector<std::pair<TypeCheckContextItem, TyTy::BaseType *>>
return_type_stack;
std::vector<TyTy::BaseType *> loop_type_stack;
StackedContexts<TypeCheckBlockContextItem> block_stack;
std::map<DefId, TraitReference> trait_context;
std::map<HirId, AssociatedImplTrait> associated_impl_traits;
// trait-id -> list of < self-tyty:impl-id>
std::map<HirId, std::vector<std::pair<const TyTy::BaseType *, HirId>>>
associated_traits_to_impls;
std::map<HirId, HirId> associated_type_mappings;
// adjustment mappings
std::map<HirId, std::vector<Adjustment>> autoderef_mappings;
std::map<HirId, std::vector<Adjustment>> cast_autoderef_mappings;
// operator overloads
std::map<HirId, TyTy::FnType *> operator_overloads;
// variants
std::map<HirId, HirId> variants;
// unconstrained type-params check
std::map<HirId, bool> unconstrained;
// predicates
std::map<HirId, TyTy::TypeBoundPredicate> predicates;
// query context lookups
std::set<HirId> querys_in_progress;
std::set<DefId> trait_queries_in_progress;
// variance analysis
TyTy::VarianceAnalysis::CrateCtx variance_analysis_ctx;
/** Used to resolve (interned) lifetime names to their bounding scope. */
class LifetimeResolver
{
/**
* The level of nested scopes, where the lifetime was declared.
*
* Index 0 is used for `impl` blocks and is skipped if not explicitly
* requested.
* Index 1 for the top-level of declarations of items.
* Index >1 is used for late-bound lifetimes.
*/
using ScopeIndex = size_t;
static constexpr ScopeIndex IMPL_SCOPE = 0;
static constexpr ScopeIndex ITEM_SCOPE = 1;
/**
* A reference to a lifetime binder.
*
* This is used to resolve lifetimes to their scope.
*/
struct LifetimeBinderRef
{
uint32_t scope; //> Depth of the scope where the lifetime was declared.
uint32_t index; //> Index of the lifetime in the scope.
};
/**
* A stack of the number of lifetimes declared in each scope.
*
* Used to pop the correct number of lifetimes when leaving a scope.
*/
std::stack<uint32_t> binder_size_stack;
/**
* Merged stack of all lifetimes declared in all scopes.
*
* Use `binder_size_stack` to determine the number of lifetimes in each
* scope.
*/
std::vector<std::pair<Lifetime, LifetimeBinderRef>> lifetime_lookup;
/**
* Whether the current scope is a function body.
*
* In function header, lifetimes are resolved as early-bound, in the body as
* named. This is because the header can be also used in call position.
*/
bool is_body = false;
/** Return the number of the current scope. */
WARN_UNUSED_RESULT uint32_t get_current_scope () const
{
return binder_size_stack.size () - 1;
}
public:
/** Add new declaration of a lifetime. */
void insert_mapping (Lifetime placeholder)
{
lifetime_lookup.push_back (
{placeholder, {get_current_scope (), binder_size_stack.top ()++}});
}
WARN_UNUSED_RESULT tl::optional<TyTy::Region>
resolve (const Lifetime &placeholder) const;
/** Only to be used by the guard. */
void push_binder () { binder_size_stack.push (0); }
/** Only to be used by the guard. */
void pop_binder () { binder_size_stack.pop (); }
bool binder_empty () { return binder_size_stack.empty (); }
/**
* Switch from resolving a function header to a function body.
*/
void switch_to_fn_body () { this->is_body = true; }
size_t get_num_bound_regions () const { return binder_size_stack.top (); }
};
// lifetime resolving
std::unordered_map<std::string, Lifetime> lifetime_name_interner;
Lifetime next_lifetime_index = Lifetime (Lifetime::FIRST_NAMED_LIFETIME);
/**
* Stack of lifetime resolvers.
*
* Due to the contruction of the type checker, it is possible to start
* resolution of a new type in the middle of resolving another type. This
* stack isolates the conexts in such cases.
*/
std::stack<LifetimeResolver> lifetime_resolver_stack;
public:
WARN_UNUSED_RESULT LifetimeResolver &get_lifetime_resolver ()
{
rust_assert (!lifetime_resolver_stack.empty ());
return lifetime_resolver_stack.top ();
}
WARN_UNUSED_RESULT const LifetimeResolver &get_lifetime_resolver () const
{
rust_assert (!lifetime_resolver_stack.empty ());
return lifetime_resolver_stack.top ();
}
/**
* A guard that pushes a new lifetime resolver on the stack and pops it
* when it goes out of scope.
*/
class LifetimeResolverGuard
{
public:
/** The kind of scope that is being pushed. */
enum ScopeKind
{
IMPL_BLOCK_RESOLVER, //> A new `impl` block scope.
RESOLVER, //> A new scope for a function body.
BINDER, //> A new scope for late-bound lifetimes.
};
private:
TypeCheckContext &ctx;
ScopeKind kind;
public:
LifetimeResolverGuard (TypeCheckContext &ctx, ScopeKind kind)
: ctx (ctx), kind (kind)
{
if (kind == IMPL_BLOCK_RESOLVER)
{
ctx.lifetime_resolver_stack.push (LifetimeResolver ());
}
if (kind == RESOLVER)
{
ctx.lifetime_resolver_stack.push (LifetimeResolver ());
// Skip the `impl` block scope.
ctx.lifetime_resolver_stack.top ().push_binder ();
}
rust_assert (!ctx.lifetime_resolver_stack.empty ());
ctx.lifetime_resolver_stack.top ().push_binder ();
}
~LifetimeResolverGuard ()
{
rust_assert (!ctx.lifetime_resolver_stack.empty ());
if (!ctx.lifetime_resolver_stack.top ().binder_empty ())
ctx.lifetime_resolver_stack.top ().pop_binder ();
if (kind == RESOLVER)
{
ctx.lifetime_resolver_stack.pop ();
}
}
};
/** Start new late bound lifetime scope. */
WARN_UNUSED_RESULT LifetimeResolverGuard push_lifetime_binder ()
{
return LifetimeResolverGuard (*this, LifetimeResolverGuard::BINDER);
}
/** Start new function body scope. */
WARN_UNUSED_RESULT LifetimeResolverGuard
push_clean_lifetime_resolver (bool is_impl_block = false)
{
return LifetimeResolverGuard (*this,
is_impl_block
? LifetimeResolverGuard::IMPL_BLOCK_RESOLVER
: LifetimeResolverGuard::RESOLVER);
}
/** Switch from resolving a function header to a function body. */
void switch_to_fn_body ()
{
this->lifetime_resolver_stack.top ().switch_to_fn_body ();
}
};
class TypeResolution
{
public:
static void Resolve (HIR::Crate &crate);
};
class TraitQueryGuard
{
public:
TraitQueryGuard (DefId id) : id (id), ctx (*TypeCheckContext::get ())
{
ctx.insert_trait_query (id);
}
~TraitQueryGuard () { ctx.trait_query_completed (id); }
private:
DefId id;
TypeCheckContext &ctx;
};
} // namespace Resolver
} // namespace Rust
#endif // RUST_HIR_TYPE_CHECK