]> git.lizzy.rs Git - rust.git/blobdiff - crates/hir_def/src/type_ref.rs
parameters.split_last()
[rust.git] / crates / hir_def / src / type_ref.rs
index 0832371c0dcf17fa84744e709540222968e21933..ee8ef6caa306a1dc72e8db72441741b94a09aa01 100644 (file)
@@ -1,16 +1,14 @@
 //! 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 {
@@ -77,6 +75,10 @@ pub(crate) fn from_ast(ctx: &LowerCtx, node: ast::Type) -> Option<Self> {
 }
 
 /// 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,
@@ -85,14 +87,16 @@ pub enum TypeRef {
     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,
 }
 
@@ -117,17 +121,25 @@ pub fn missing() -> LifetimeRef {
 
 #[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())
             }
@@ -141,18 +153,27 @@ pub(crate) fn from_ast(ctx: &LowerCtx, node: ast::Type) -> Self {
                     .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(&lt));
                 let mutability = Mutability::from_mutable(inner.mut_token().is_some());
                 TypeRef::Reference(Box::new(inner_ty), lifetime, mutability)
@@ -170,15 +191,26 @@ pub(crate) fn from_ast(ctx: &LowerCtx, node: ast::Type) -> Self {
                         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()))
             }
@@ -196,10 +228,9 @@ pub(crate) fn from_ast(ctx: &LowerCtx, node: ast::Type) -> Self {
     }
 
     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,
         }
     }
 
@@ -207,33 +238,26 @@ pub(crate) fn unit() -> TypeRef {
         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(&param_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 => (),
                         }
                     }
@@ -262,8 +286,10 @@ fn go_path(path: &Path, f: &mut impl FnMut(&TypeRef)) {
                             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 => (),
                             }
                         }
@@ -277,9 +303,9 @@ fn go_path(path: &Path, f: &mut impl FnMut(&TypeRef)) {
 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![]
     }
@@ -287,96 +313,101 @@ pub(crate) fn type_bounds_from_ast(
 
 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(&lt)))
+                        .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, &macro_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)
     }
 }