//! HIR for references to types. Paths in these are not yet resolved. They can
//! be directly created from an ast::TypeRef, without further queries.
-use std::borrow::Cow;
-use hir_expand::{ast_id_map::FileAstId, name::Name, ExpandResult, InFile};
-use syntax::{algo::SyntaxRewriter, ast, AstNode, SyntaxKind, SyntaxNode};
-
-use crate::{
- body::{Expander, LowerCtx},
- db::DefDatabase,
- path::Path,
- ModuleId,
+use hir_expand::{
+ name::{AsName, Name},
+ AstId, InFile,
};
+use std::convert::TryInto;
+use syntax::ast::{self, HasName};
+
+use crate::{body::LowerCtx, intern::Interned, path::Path};
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum Mutability {
}
/// Compare ty::Ty
+///
+/// Note: Most users of `TypeRef` that end up in the salsa database intern it using
+/// `Interned<TypeRef>` to save space. But notably, nested `TypeRef`s are not interned, since that
+/// does not seem to save any noticeable amount of memory.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum TypeRef {
Never,
Path(Path),
RawPtr(Box<TypeRef>, Mutability),
Reference(Box<TypeRef>, Option<LifetimeRef>, Mutability),
- Array(Box<TypeRef> /*, Expr*/),
+ // FIXME: for full const generics, the latter element (length) here is going to have to be an
+ // expression that is further lowered later in hir_ty.
+ Array(Box<TypeRef>, ConstScalar),
Slice(Box<TypeRef>),
/// A fn pointer. Last element of the vector is the return type.
- Fn(Vec<TypeRef>, bool /*varargs*/),
+ Fn(Vec<(Option<Name>, TypeRef)>, bool /*varargs*/),
// For
- ImplTrait(Vec<TypeBound>),
- DynTrait(Vec<TypeBound>),
- Macro(InFile<FileAstId<ast::MacroCall>>),
+ ImplTrait(Vec<Interned<TypeBound>>),
+ DynTrait(Vec<Interned<TypeBound>>),
+ Macro(AstId<ast::MacroCall>),
Error,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum TypeBound {
- Path(Path),
- // ForLifetime(Vec<LifetimeRef>, Path), FIXME ForLifetime
+ Path(Path, TraitBoundModifier),
+ ForLifetime(Box<[Name]>, Path),
Lifetime(LifetimeRef),
Error,
}
+/// A modifier on a bound, currently this is only used for `?Sized`, where the
+/// modifier is `Maybe`.
+#[derive(Clone, PartialEq, Eq, Hash, Debug)]
+pub enum TraitBoundModifier {
+ None,
+ Maybe,
+}
+
impl TypeRef {
/// Converts an `ast::TypeRef` to a `hir::TypeRef`.
- pub(crate) fn from_ast(ctx: &LowerCtx, node: ast::Type) -> Self {
+ pub fn from_ast(ctx: &LowerCtx, node: ast::Type) -> Self {
match node {
- ast::Type::ParenType(inner) => TypeRef::from_ast_opt(&ctx, inner.ty()),
+ ast::Type::ParenType(inner) => TypeRef::from_ast_opt(ctx, inner.ty()),
ast::Type::TupleType(inner) => {
TypeRef::Tuple(inner.fields().map(|it| TypeRef::from_ast(ctx, it)).collect())
}
.unwrap_or(TypeRef::Error)
}
ast::Type::PtrType(inner) => {
- let inner_ty = TypeRef::from_ast_opt(&ctx, inner.ty());
+ let inner_ty = TypeRef::from_ast_opt(ctx, inner.ty());
let mutability = Mutability::from_mutable(inner.mut_token().is_some());
TypeRef::RawPtr(Box::new(inner_ty), mutability)
}
ast::Type::ArrayType(inner) => {
- TypeRef::Array(Box::new(TypeRef::from_ast_opt(&ctx, inner.ty())))
+ // FIXME: This is a hack. We should probably reuse the machinery of
+ // `hir_def::body::lower` to lower this into an `Expr` and then evaluate it at the
+ // `hir_ty` level, which would allow knowing the type of:
+ // let v: [u8; 2 + 2] = [0u8; 4];
+ let len = inner
+ .expr()
+ .map(ConstScalar::usize_from_literal_expr)
+ .unwrap_or(ConstScalar::Unknown);
+
+ TypeRef::Array(Box::new(TypeRef::from_ast_opt(ctx, inner.ty())), len)
}
ast::Type::SliceType(inner) => {
- TypeRef::Slice(Box::new(TypeRef::from_ast_opt(&ctx, inner.ty())))
+ TypeRef::Slice(Box::new(TypeRef::from_ast_opt(ctx, inner.ty())))
}
ast::Type::RefType(inner) => {
- let inner_ty = TypeRef::from_ast_opt(&ctx, inner.ty());
+ let inner_ty = TypeRef::from_ast_opt(ctx, inner.ty());
let lifetime = inner.lifetime().map(|lt| LifetimeRef::new(<));
let mutability = Mutability::from_mutable(inner.mut_token().is_some());
TypeRef::Reference(Box::new(inner_ty), lifetime, mutability)
is_varargs = param.dotdotdot_token().is_some();
}
- pl.params().map(|p| p.ty()).map(|it| TypeRef::from_ast_opt(&ctx, it)).collect()
+ pl.params()
+ .map(|it| {
+ let type_ref = TypeRef::from_ast_opt(ctx, it.ty());
+ let name = match it.pat() {
+ Some(ast::Pat::IdentPat(it)) => Some(
+ it.name().map(|nr| nr.as_name()).unwrap_or_else(Name::missing),
+ ),
+ _ => None,
+ };
+ (name, type_ref)
+ })
+ .collect()
} else {
Vec::new()
};
- params.push(ret_ty);
+ params.push((None, ret_ty));
TypeRef::Fn(params, is_varargs)
}
// for types are close enough for our purposes to the inner type for now...
- ast::Type::ForType(inner) => TypeRef::from_ast_opt(&ctx, inner.ty()),
+ ast::Type::ForType(inner) => TypeRef::from_ast_opt(ctx, inner.ty()),
ast::Type::ImplTraitType(inner) => {
TypeRef::ImplTrait(type_bounds_from_ast(ctx, inner.type_bound_list()))
}
}
pub(crate) fn from_ast_opt(ctx: &LowerCtx, node: Option<ast::Type>) -> Self {
- if let Some(node) = node {
- TypeRef::from_ast(ctx, node)
- } else {
- TypeRef::Error
+ match node {
+ Some(node) => TypeRef::from_ast(ctx, node),
+ None => TypeRef::Error,
}
}
TypeRef::Tuple(Vec::new())
}
- pub fn has_macro_calls(&self) -> bool {
- let mut has_macro_call = false;
- self.walk(&mut |ty_ref| {
- if let TypeRef::Macro(_) = ty_ref {
- has_macro_call |= true
- }
- });
- has_macro_call
- }
-
pub fn walk(&self, f: &mut impl FnMut(&TypeRef)) {
go(self, f);
fn go(type_ref: &TypeRef, f: &mut impl FnMut(&TypeRef)) {
f(type_ref);
match type_ref {
- TypeRef::Fn(types, _) | TypeRef::Tuple(types) => {
- types.iter().for_each(|t| go(t, f))
+ TypeRef::Fn(params, _) => {
+ params.iter().for_each(|(_, param_type)| go(¶m_type, f))
}
+ TypeRef::Tuple(types) => types.iter().for_each(|t| go(t, f)),
TypeRef::RawPtr(type_ref, _)
| TypeRef::Reference(type_ref, ..)
- | TypeRef::Array(type_ref)
- | TypeRef::Slice(type_ref) => go(&type_ref, f),
+ | TypeRef::Array(type_ref, _)
+ | TypeRef::Slice(type_ref) => go(type_ref, f),
TypeRef::ImplTrait(bounds) | TypeRef::DynTrait(bounds) => {
for bound in bounds {
- match bound {
- TypeBound::Path(path) => go_path(path, f),
+ match bound.as_ref() {
+ TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => {
+ go_path(path, f)
+ }
TypeBound::Lifetime(_) | TypeBound::Error => (),
}
}
go(type_ref, f);
}
for bound in &binding.bounds {
- match bound {
- TypeBound::Path(path) => go_path(path, f),
+ match bound.as_ref() {
+ TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => {
+ go_path(path, f)
+ }
TypeBound::Lifetime(_) | TypeBound::Error => (),
}
}
pub(crate) fn type_bounds_from_ast(
lower_ctx: &LowerCtx,
type_bounds_opt: Option<ast::TypeBoundList>,
-) -> Vec<TypeBound> {
+) -> Vec<Interned<TypeBound>> {
if let Some(type_bounds) = type_bounds_opt {
- type_bounds.bounds().map(|it| TypeBound::from_ast(lower_ctx, it)).collect()
+ type_bounds.bounds().map(|it| Interned::new(TypeBound::from_ast(lower_ctx, it))).collect()
} else {
vec![]
}
impl TypeBound {
pub(crate) fn from_ast(ctx: &LowerCtx, node: ast::TypeBound) -> Self {
+ let lower_path_type = |path_type: ast::PathType| ctx.lower_path(path_type.path()?);
+
match node.kind() {
ast::TypeBoundKind::PathType(path_type) => {
- let path = match path_type.path() {
- Some(p) => p,
- None => return TypeBound::Error,
+ let m = match node.question_mark_token() {
+ Some(_) => TraitBoundModifier::Maybe,
+ None => TraitBoundModifier::None,
};
-
- let path = match ctx.lower_path(path) {
- Some(p) => p,
- None => return TypeBound::Error,
+ lower_path_type(path_type)
+ .map(|p| TypeBound::Path(p, m))
+ .unwrap_or(TypeBound::Error)
+ }
+ ast::TypeBoundKind::ForType(for_type) => {
+ let lt_refs = match for_type.generic_param_list() {
+ Some(gpl) => gpl
+ .lifetime_params()
+ .flat_map(|lp| lp.lifetime().map(|lt| Name::new_lifetime(<)))
+ .collect(),
+ None => Box::default(),
};
- TypeBound::Path(path)
+ let path = for_type.ty().and_then(|ty| match ty {
+ ast::Type::PathType(path_type) => lower_path_type(path_type),
+ _ => None,
+ });
+ match path {
+ Some(p) => TypeBound::ForLifetime(lt_refs, p),
+ None => TypeBound::Error,
+ }
}
- ast::TypeBoundKind::ForType(_) => TypeBound::Error, // FIXME ForType
ast::TypeBoundKind::Lifetime(lifetime) => {
TypeBound::Lifetime(LifetimeRef::new(&lifetime))
}
}
}
- pub fn as_path(&self) -> Option<&Path> {
+ pub fn as_path(&self) -> Option<(&Path, &TraitBoundModifier)> {
match self {
- TypeBound::Path(p) => Some(p),
- _ => None,
+ TypeBound::Path(p, m) => Some((p, m)),
+ TypeBound::ForLifetime(_, p) => Some((p, &TraitBoundModifier::None)),
+ TypeBound::Lifetime(_) | TypeBound::Error => None,
}
}
}
-pub fn expand_type_ref<'a>(
- db: &dyn DefDatabase,
- module_id: ModuleId,
- type_ref: &'a TypeRef,
-) -> Option<Cow<'a, TypeRef>> {
- let macro_call = match type_ref {
- TypeRef::Macro(macro_call) => macro_call,
- _ => return Some(Cow::Borrowed(type_ref)),
- };
-
- let file_id = macro_call.file_id;
- let macro_call = macro_call.to_node(db.upcast());
-
- let mut expander = Expander::new(db, file_id, module_id);
- let expanded = expand(db, &mut expander, ¯o_call, true)?;
-
- let node = ast::Type::cast(expanded)?;
-
- let ctx = LowerCtx::new(db, file_id);
- return Some(Cow::Owned(TypeRef::from_ast(&ctx, node)));
-
- fn expand(
- db: &dyn DefDatabase,
- expander: &mut Expander,
- macro_call: &ast::MacroCall,
- expect_type: bool,
- ) -> Option<SyntaxNode> {
- let (mark, mut expanded) = match expander.enter_expand_raw(db, macro_call.clone()) {
- Ok(ExpandResult { value: Some((mark, expanded)), .. }) => (mark, expanded),
- _ => return None,
- };
-
- if expect_type && !ast::Type::can_cast(expanded.kind()) {
- expander.exit(db, mark);
- return None;
- }
+/// A concrete constant value
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum ConstScalar {
+ // for now, we only support the trivial case of constant evaluating the length of an array
+ // Note that this is u64 because the target usize may be bigger than our usize
+ Usize(u64),
+
+ /// Case of an unknown value that rustc might know but we don't
+ // FIXME: this is a hack to get around chalk not being able to represent unevaluatable
+ // constants
+ // https://github.com/rust-analyzer/rust-analyzer/pull/8813#issuecomment-840679177
+ // https://rust-lang.zulipchat.com/#narrow/stream/144729-wg-traits/topic/Handling.20non.20evaluatable.20constants'.20equality/near/238386348
+ Unknown,
+}
- if ast::MacroType::can_cast(expanded.kind()) {
- expanded = expanded.first_child()?; // MACRO_CALL
+impl std::fmt::Display for ConstScalar {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ match self {
+ ConstScalar::Usize(us) => write!(fmt, "{}", us),
+ ConstScalar::Unknown => write!(fmt, "_"),
}
+ }
+}
- let mut rewriter = SyntaxRewriter::default();
+impl ConstScalar {
+ /// Gets a target usize out of the ConstScalar
+ pub fn as_usize(&self) -> Option<u64> {
+ match self {
+ &ConstScalar::Usize(us) => Some(us),
+ _ => None,
+ }
+ }
- let children = expanded.descendants().filter_map(ast::MacroCall::cast);
- for child in children {
- if let Some(new_node) = expand(db, expander, &child, false) {
- if expanded == *child.syntax() {
- expanded = new_node;
- } else {
- let parent = child.syntax().parent();
- let old_node = match &parent {
- Some(node) if node.kind() == SyntaxKind::MACRO_TYPE => node,
- _ => child.syntax(),
- };
- rewriter.replace(old_node, &new_node)
+ // FIXME: as per the comments on `TypeRef::Array`, this evaluation should not happen at this
+ // parse stage.
+ fn usize_from_literal_expr(expr: ast::Expr) -> ConstScalar {
+ match expr {
+ ast::Expr::Literal(lit) => {
+ let lkind = lit.kind();
+ match lkind {
+ ast::LiteralKind::IntNumber(num)
+ if num.suffix() == None || num.suffix() == Some("usize") =>
+ {
+ num.value().and_then(|v| v.try_into().ok())
+ }
+ _ => None,
}
}
+ _ => None,
}
-
- expander.exit(db, mark);
-
- let res = rewriter.rewrite(&expanded);
- Some(res)
+ .map(ConstScalar::Usize)
+ .unwrap_or(ConstScalar::Unknown)
}
}