Semantic getter

This commit is contained in:
Andrzej Głuszak 2021-09-29 14:04:32 +02:00
parent 6c7526d308
commit 98676efdc5
9 changed files with 401 additions and 63 deletions

View file

@ -1574,6 +1574,11 @@ pub struct BuiltinType {
}
impl BuiltinType {
// FIXME: I'm not sure if it's the best place to put it
pub fn str() -> BuiltinType {
BuiltinType { inner: hir_def::builtin_type::BuiltinType::Str }
}
pub fn ty(self, db: &dyn HirDatabase, module: Module) -> Type {
let resolver = module.id.resolver(db.upcast());
Type::new_with_resolver(db, &resolver, TyBuilder::builtin(self.inner))
@ -2263,6 +2268,11 @@ impl Type {
Type::new(db, krate, def, ty)
}
// FIXME: No idea where to put it
pub fn make_slice_of(self) -> Type {
Type { krate: self.krate, env: self.env, ty: TyBuilder::slice(self.ty) }
}
pub fn is_unit(&self) -> bool {
matches!(self.ty.kind(&Interner), TyKind::Tuple(0, ..))
}

View file

@ -97,6 +97,10 @@ impl TyBuilder<()> {
}
}
pub fn slice(argument: Ty) -> Ty {
TyKind::Slice(argument).intern(&Interner)
}
pub fn type_params_subst(db: &dyn HirDatabase, def: impl Into<GenericDefId>) -> Substitution {
let params = generics(db.upcast(), def.into());
params.type_params_subst(db)

View file

@ -2268,8 +2268,8 @@ fn foo() {
file_id: FileId(
1,
),
full_range: 254..436,
focus_range: 293..299,
full_range: 276..458,
focus_range: 315..321,
name: "Future",
kind: Trait,
description: "pub trait Future",

View file

@ -1,3 +1,5 @@
use rustc_hash::{FxHashMap, FxHashSet};
use hir::{HasSource, HirDisplay, Module, ModuleDef, Semantics, TypeInfo};
use ide_db::{
base_db::FileId,
@ -5,7 +7,6 @@ use ide_db::{
helpers::SnippetCap,
RootDatabase,
};
use rustc_hash::{FxHashMap, FxHashSet};
use stdx::to_lower_snake_case;
use syntax::{
ast::{
@ -17,7 +18,7 @@ use syntax::{
};
use crate::{
utils::useless_type_special_case,
utils::convert_reference_type,
utils::{find_struct_impl, render_snippet, Cursor},
AssistContext, AssistId, AssistKind, Assists,
};
@ -424,19 +425,7 @@ fn fn_args(
let mut arg_types = Vec::new();
for arg in call.arg_list()?.args() {
arg_names.push(fn_arg_name(&ctx.sema, &arg));
arg_types.push(match fn_arg_type(ctx, target_module, &arg) {
Some(ty) => {
if !ty.is_empty() && ty.starts_with('&') {
match useless_type_special_case("", &ty[1..].to_owned()) {
Some((new_ty, _)) => new_ty,
None => ty,
}
} else {
ty
}
}
None => String::from("_"),
});
arg_types.push(fn_arg_type(ctx, target_module, &arg));
}
deduplicate_arg_names(&mut arg_names);
let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| {
@ -511,17 +500,31 @@ fn fn_arg_name(sema: &Semantics<RootDatabase>, arg_expr: &ast::Expr) -> String {
}
}
fn fn_arg_type(
ctx: &AssistContext,
target_module: hir::Module,
fn_arg: &ast::Expr,
) -> Option<String> {
let ty = ctx.sema.type_of_expr(fn_arg)?.adjusted();
if ty.is_unknown() {
return None;
fn fn_arg_type(ctx: &AssistContext, target_module: hir::Module, fn_arg: &ast::Expr) -> String {
fn maybe_displayed_type(
ctx: &AssistContext,
target_module: hir::Module,
fn_arg: &ast::Expr,
) -> Option<String> {
let ty = ctx.sema.type_of_expr(fn_arg)?.adjusted();
if ty.is_unknown() {
return None;
}
if ty.is_reference() || ty.is_mutable_reference() {
convert_reference_type(
ty.strip_references(),
ctx,
ctx.sema.scope(fn_arg.syntax()).krate(),
)
.map(|conversion| conversion.convert_type(ctx.db()))
.or_else(|| ty.display_source_code(ctx.db(), target_module.into()).ok())
} else {
ty.display_source_code(ctx.db(), target_module.into()).ok()
}
}
ty.display_source_code(ctx.db(), target_module.into()).ok()
maybe_displayed_type(ctx, target_module, fn_arg).unwrap_or_else(|| String::from("_"))
}
/// Returns the position inside the current mod or file

View file

@ -2,8 +2,7 @@ use stdx::{format_to, to_lower_snake_case};
use syntax::ast::{self, AstNode, HasName, HasVisibility};
use crate::{
utils::useless_type_special_case,
utils::{find_impl_block_end, find_struct_impl, generate_impl_text},
utils::{convert_reference_type, find_impl_block_end, find_struct_impl, generate_impl_text},
AssistContext, AssistId, AssistKind, Assists, GroupLabel,
};
@ -12,12 +11,27 @@ use crate::{
// Generate a getter method.
//
// ```
// # //- minicore: as_ref
// # pub struct String;
// # impl AsRef<str> for String {
// # fn as_ref(&self) -> &str {
// # ""
// # }
// # }
// #
// struct Person {
// nam$0e: String,
// }
// ```
// ->
// ```
// # pub struct String;
// # impl AsRef<str> for String {
// # fn as_ref(&self) -> &str {
// # ""
// # }
// # }
// #
// struct Person {
// name: String,
// }
@ -25,7 +39,7 @@ use crate::{
// impl Person {
// /// Get a reference to the person's name.
// fn $0name(&self) -> &str {
// self.name.as_str()
// self.name.as_ref()
// }
// }
// ```
@ -98,9 +112,23 @@ pub(crate) fn generate_getter_impl(
let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
let (ty, body) = if mutable {
(format!("&mut {}", field_ty), format!("&mut self.{}", field_name))
(
format!("&mut {}", field_ty.to_string()),
format!("&mut self.{}", field_name.to_string()),
)
} else {
useless_type_special_case(&field_name.to_string(), &field_ty.to_string())
ctx.sema
.resolve_type(&field_ty)
.and_then(|ty| {
convert_reference_type(ty, ctx, ctx.sema.scope(field_ty.syntax()).krate())
})
.map(|conversion| {
cov_mark::hit!(convert_reference_type);
(
conversion.convert_type(ctx.db()),
conversion.getter(field_name.to_string()),
)
})
.unwrap_or_else(|| (format!("&{}", field_ty), format!("&self.{}", field_name)))
};
@ -284,30 +312,113 @@ impl Context {
}
#[test]
fn test_special_cases() {
cov_mark::check!(useless_type_special_case);
fn test_not_a_special_case() {
cov_mark::check_count!(convert_reference_type, 0);
// Fake string which doesn't implement AsRef<str>
check_assist(
generate_getter,
r#"
pub struct String;
struct S { foo: $0String }
"#,
r#"
pub struct String;
struct S { foo: String }
impl S {
/// Get a reference to the s's foo.
fn $0foo(&self) -> &String {
&self.foo
}
}
"#,
);
}
#[test]
fn test_convert_reference_type() {
cov_mark::check_count!(convert_reference_type, 6);
// Copy
check_assist(
generate_getter,
r#"
//- minicore: copy
struct S { foo: $0bool }
"#,
r#"
struct S { foo: bool }
impl S {
/// Get a reference to the s's foo.
fn $0foo(&self) -> bool {
self.foo
}
}
"#,
);
// AsRef<str>
check_assist(
generate_getter,
r#"
//- minicore: as_ref
pub struct String;
impl AsRef<str> for String {
fn as_ref(&self) -> &str {
""
}
}
struct S { foo: $0String }
"#,
r#"
pub struct String;
impl AsRef<str> for String {
fn as_ref(&self) -> &str {
""
}
}
struct S { foo: String }
impl S {
/// Get a reference to the s's foo.
fn $0foo(&self) -> &str {
self.foo.as_str()
self.foo.as_ref()
}
}
"#,
);
// AsRef<T>
check_assist(
generate_getter,
r#"
//- minicore: as_ref
struct Sweets;
pub struct Box<T>(T);
impl<T> AsRef<T> for Box<T> {
fn as_ref(&self) -> &T {
&self.0
}
}
struct S { foo: $0Box<Sweets> }
"#,
r#"
struct Sweets;
pub struct Box<T>(T);
impl<T> AsRef<T> for Box<T> {
fn as_ref(&self) -> &T {
&self.0
}
}
struct S { foo: Box<Sweets> }
impl S {
@ -318,28 +429,52 @@ impl S {
}
"#,
);
// AsRef<[T]>
check_assist(
generate_getter,
r#"
//- minicore: as_ref
pub struct Vec<T>;
impl<T> AsRef<[T]> for Vec<T> {
fn as_ref(&self) -> &[T] {
&[]
}
}
struct S { foo: $0Vec<()> }
"#,
r#"
pub struct Vec<T>;
impl<T> AsRef<[T]> for Vec<T> {
fn as_ref(&self) -> &[T] {
&[]
}
}
struct S { foo: Vec<()> }
impl S {
/// Get a reference to the s's foo.
fn $0foo(&self) -> &[()] {
self.foo.as_slice()
self.foo.as_ref()
}
}
"#,
);
// Option
check_assist(
generate_getter,
r#"
//- minicore: option
struct Failure;
struct S { foo: $0Option<Failure> }
"#,
r#"
struct Failure;
struct S { foo: Option<Failure> }
impl S {
@ -348,6 +483,29 @@ impl S {
self.foo.as_ref()
}
}
"#,
);
// Result
check_assist(
generate_getter,
r#"
//- minicore: result
struct Context {
dat$0a: Result<bool, i32>,
}
"#,
r#"
struct Context {
data: Result<bool, i32>,
}
impl Context {
/// Get a reference to the context's data.
fn $0data(&self) -> Result<&bool, &i32> {
self.data.as_ref()
}
}
"#,
);
}

View file

@ -951,11 +951,26 @@ fn doctest_generate_getter() {
check_doc_test(
"generate_getter",
r#####"
//- minicore: as_ref
pub struct String;
impl AsRef<str> for String {
fn as_ref(&self) -> &str {
""
}
}
struct Person {
nam$0e: String,
}
"#####,
r#####"
pub struct String;
impl AsRef<str> for String {
fn as_ref(&self) -> &str {
""
}
}
struct Person {
name: String,
}
@ -963,7 +978,7 @@ struct Person {
impl Person {
/// Get a reference to the person's name.
fn $0name(&self) -> &str {
self.name.as_str()
self.name.as_ref()
}
}
"#####,

View file

@ -1,13 +1,14 @@
//! Assorted functions shared by several assists.
pub(crate) mod suggest_name;
mod gen_trait_fn_body;
use std::ops;
use hir::HasSource;
use ide_db::{helpers::SnippetCap, path_transform::PathTransform, RootDatabase};
use itertools::Itertools;
pub(crate) use gen_trait_fn_body::gen_trait_fn_body;
use hir::db::HirDatabase;
use hir::{Crate, HasSource, HirDisplay};
use ide_db::helpers::FamousDefs;
use ide_db::{helpers::SnippetCap, path_transform::PathTransform, RootDatabase};
use stdx::format_to;
use syntax::{
ast::{
@ -23,7 +24,8 @@ use syntax::{
use crate::assist_context::{AssistBuilder, AssistContext};
pub(crate) use gen_trait_fn_body::gen_trait_fn_body;
pub(crate) mod suggest_name;
mod gen_trait_fn_body;
pub(crate) fn unwrap_trivial_block(block_expr: ast::BlockExpr) -> ast::Expr {
extract_trivial_expression(&block_expr)
@ -507,27 +509,156 @@ pub(crate) fn add_method_to_adt(
builder.insert(start_offset, buf);
}
pub fn useless_type_special_case(field_name: &str, field_ty: &String) -> Option<(String, String)> {
if field_ty == "String" {
cov_mark::hit!(useless_type_special_case);
return Some(("&str".to_string(), format!("self.{}.as_str()", field_name)));
}
if let Some(arg) = ty_ctor(field_ty, "Vec") {
return Some((format!("&[{}]", arg), format!("self.{}.as_slice()", field_name)));
}
if let Some(arg) = ty_ctor(field_ty, "Box") {
return Some((format!("&{}", arg), format!("self.{}.as_ref()", field_name)));
}
if let Some(arg) = ty_ctor(field_ty, "Option") {
return Some((format!("Option<&{}>", arg), format!("self.{}.as_ref()", field_name)));
}
None
#[derive(Debug)]
pub(crate) struct ReferenceConversion {
conversion: ReferenceConversionType,
ty: hir::Type,
}
// FIXME: This should rely on semantic info.
fn ty_ctor(ty: &String, ctor: &str) -> Option<String> {
let res = ty.to_string().strip_prefix(ctor)?.strip_prefix('<')?.strip_suffix('>')?.to_string();
Some(res)
#[derive(Debug)]
enum ReferenceConversionType {
// reference can be stripped if the type is Copy
Copy,
// &String -> &str
AsRefStr,
// &Vec<T> -> &[T]
AsRefSlice,
// &Box<T> -> &T
Dereferenced,
// &Option<T> -> Option<&T>
Option,
// &Result<T, E> -> Result<&T, &E>
Result,
}
impl ReferenceConversion {
pub(crate) fn convert_type(&self, db: &dyn HirDatabase) -> String {
match self.conversion {
ReferenceConversionType::Copy => self.ty.display(db).to_string(),
ReferenceConversionType::AsRefStr => "&str".to_string(),
ReferenceConversionType::AsRefSlice => {
let type_argument_name =
self.ty.type_arguments().next().unwrap().display(db).to_string();
format!("&[{}]", type_argument_name)
}
ReferenceConversionType::Dereferenced => {
let type_argument_name =
self.ty.type_arguments().next().unwrap().display(db).to_string();
format!("&{}", type_argument_name)
}
ReferenceConversionType::Option => {
let type_argument_name =
self.ty.type_arguments().next().unwrap().display(db).to_string();
format!("Option<&{}>", type_argument_name)
}
ReferenceConversionType::Result => {
let mut type_arguments = self.ty.type_arguments();
let first_type_argument_name =
type_arguments.next().unwrap().display(db).to_string();
let second_type_argument_name =
type_arguments.next().unwrap().display(db).to_string();
format!("Result<&{}, &{}>", first_type_argument_name, second_type_argument_name)
}
}
}
pub(crate) fn getter(&self, field_name: String) -> String {
match self.conversion {
ReferenceConversionType::Copy => format!("self.{}", field_name),
ReferenceConversionType::AsRefStr
| ReferenceConversionType::AsRefSlice
| ReferenceConversionType::Dereferenced
| ReferenceConversionType::Option
| ReferenceConversionType::Result => format!("self.{}.as_ref()", field_name),
}
}
}
// FIXME: It should return a new hir::Type, but currently constructing new types is too cumbersome
// and all users of this function operate on string type names, so they can do the conversion
// itself themselves.
// Another problem is that it probably shouldn't take AssistContext as a parameter, as
// it should be usable not only in assists.
pub(crate) fn convert_reference_type(
ty: hir::Type,
ctx: &AssistContext,
krate: Option<Crate>,
) -> Option<ReferenceConversion> {
let sema = &ctx.sema;
let db = sema.db;
let famous_defs = &FamousDefs(sema, krate);
handle_copy(&ty, db)
.or_else(|| handle_as_ref_str(&ty, db, famous_defs, ctx))
.or_else(|| handle_as_ref_slice(&ty, db, famous_defs))
.or_else(|| handle_dereferenced(&ty, db, famous_defs))
.or_else(|| handle_option_as_ref(&ty, db, famous_defs))
.or_else(|| handle_result_as_ref(&ty, db, famous_defs))
.map(|conversion| ReferenceConversion { ty, conversion })
}
fn handle_copy(ty: &hir::Type, db: &dyn HirDatabase) -> Option<ReferenceConversionType> {
ty.is_copy(db).then(|| ReferenceConversionType::Copy)
}
fn handle_as_ref_str(
ty: &hir::Type,
db: &dyn HirDatabase,
famous_defs: &FamousDefs,
ctx: &AssistContext,
) -> Option<ReferenceConversionType> {
let module = ctx.sema.to_module_def(ctx.file_id())?;
let str_type = hir::BuiltinType::str().ty(db, module);
ty.impls_trait(db, famous_defs.core_convert_AsRef()?, &[str_type])
.then(|| ReferenceConversionType::AsRefStr)
}
fn handle_as_ref_slice(
ty: &hir::Type,
db: &dyn HirDatabase,
famous_defs: &FamousDefs,
) -> Option<ReferenceConversionType> {
let type_argument = ty.type_arguments().next()?;
let slice_type = type_argument.make_slice_of();
ty.impls_trait(db, famous_defs.core_convert_AsRef()?, &[slice_type])
.then(|| ReferenceConversionType::AsRefSlice)
}
fn handle_dereferenced(
ty: &hir::Type,
db: &dyn HirDatabase,
famous_defs: &FamousDefs,
) -> Option<ReferenceConversionType> {
let type_argument = ty.type_arguments().next()?;
ty.impls_trait(db, famous_defs.core_convert_AsRef()?, &[type_argument])
.then(|| ReferenceConversionType::Dereferenced)
}
fn handle_option_as_ref(
ty: &hir::Type,
db: &dyn HirDatabase,
famous_defs: &FamousDefs,
) -> Option<ReferenceConversionType> {
if ty.as_adt() == famous_defs.core_option_Option()?.ty(db).as_adt() {
Some(ReferenceConversionType::Option)
} else {
None
}
}
fn handle_result_as_ref(
ty: &hir::Type,
db: &dyn HirDatabase,
famous_defs: &FamousDefs,
) -> Option<ReferenceConversionType> {
if ty.as_adt() == famous_defs.core_result_Result()?.ty(db).as_adt() {
Some(ReferenceConversionType::Result)
} else {
None
}
}
pub(crate) fn get_methods(items: &ast::AssocItemList) -> Vec<ast::Fn> {

View file

@ -68,10 +68,18 @@ impl FamousDefs<'_, '_> {
self.find_trait("core:ops:Deref")
}
pub fn core_convert_AsRef(&self) -> Option<Trait> {
self.find_trait("core:convert:AsRef")
}
pub fn core_ops_ControlFlow(&self) -> Option<Enum> {
self.find_enum("core:ops:ControlFlow")
}
pub fn core_marker_Copy(&self) -> Option<Trait> {
self.find_trait("core:marker:Copy")
}
pub fn alloc(&self) -> Option<Crate> {
self.find_crate("alloc")
}

View file

@ -35,6 +35,7 @@
//! fmt: result
//! bool_impl: option, fn
//! add:
//! as_ref: sized
pub mod marker {
// region:sized
@ -113,8 +114,9 @@ pub mod clone {
}
// endregion:clone
// region:from
pub mod convert {
// region:from
pub trait From<T>: Sized {
fn from(_: T) -> Self;
}
@ -136,8 +138,14 @@ pub mod convert {
t
}
}
// endregion:from
// region:as_ref
pub trait AsRef<T: ?Sized> {
fn as_ref(&self) -> &T;
}
// endregion:as_ref
}
// endregion:from
pub mod ops {
// region:coerce_unsized
@ -609,6 +617,7 @@ pub mod prelude {
cmp::{Eq, PartialEq}, // :eq
cmp::{Ord, PartialOrd}, // :ord
convert::{From, Into}, // :from
convert::AsRef, // :as_ref
default::Default, // :default
iter::{IntoIterator, Iterator}, // :iterator
macros::builtin::derive, // :derive