mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-13 21:54:42 +00:00
Auto merge of #17268 - Veykril:signatures, r=Veykril
feat: More callable info With this PR we retain more info about callables other than functions, allowing for closure parameter type inlay hints to be linkable as well as better signature help around closures and `Fn*` implementors.
This commit is contained in:
commit
daf66ad8eb
17 changed files with 387 additions and 225 deletions
|
@ -12,9 +12,6 @@ use std::sync::OnceLock;
|
|||
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
/// Ignored attribute namespaces used by tools.
|
||||
pub const TOOL_MODULES: &[&str] = &["rustfmt", "clippy"];
|
||||
|
||||
pub struct BuiltinAttribute {
|
||||
pub name: &'static str,
|
||||
pub template: AttributeTemplate,
|
||||
|
|
|
@ -84,6 +84,14 @@ use crate::{
|
|||
LocalModuleId, Lookup, MacroExpander, MacroId, ModuleId, ProcMacroId, UseId,
|
||||
};
|
||||
|
||||
const PREDEFINED_TOOLS: &[SmolStr] = &[
|
||||
SmolStr::new_static("clippy"),
|
||||
SmolStr::new_static("rustfmt"),
|
||||
SmolStr::new_static("diagnostic"),
|
||||
SmolStr::new_static("miri"),
|
||||
SmolStr::new_static("rust_analyzer"),
|
||||
];
|
||||
|
||||
/// Contains the results of (early) name resolution.
|
||||
///
|
||||
/// A `DefMap` stores the module tree and the definitions that are in scope in every module after
|
||||
|
@ -160,7 +168,7 @@ impl DefMapCrateData {
|
|||
fn_proc_macro_mapping: FxHashMap::default(),
|
||||
proc_macro_loading_error: None,
|
||||
registered_attrs: Vec::new(),
|
||||
registered_tools: Vec::new(),
|
||||
registered_tools: PREDEFINED_TOOLS.into(),
|
||||
unstable_features: FxHashSet::default(),
|
||||
rustc_coherence_is_core: false,
|
||||
no_core: false,
|
||||
|
|
|
@ -10,7 +10,7 @@ use syntax::{ast, SmolStr};
|
|||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
attr::builtin::{find_builtin_attr_idx, TOOL_MODULES},
|
||||
attr::builtin::find_builtin_attr_idx,
|
||||
db::DefDatabase,
|
||||
item_scope::BuiltinShadowMode,
|
||||
nameres::path_resolution::ResolveMode,
|
||||
|
@ -82,8 +82,7 @@ impl DefMap {
|
|||
let name = name.to_smol_str();
|
||||
let pred = |n: &_| *n == name;
|
||||
|
||||
let registered = self.data.registered_tools.iter().map(SmolStr::as_str);
|
||||
let is_tool = TOOL_MODULES.iter().copied().chain(registered).any(pred);
|
||||
let is_tool = self.data.registered_tools.iter().map(SmolStr::as_str).any(pred);
|
||||
// FIXME: tool modules can be shadowed by actual modules
|
||||
if is_tool {
|
||||
return true;
|
||||
|
|
|
@ -797,19 +797,22 @@ impl<'a> InferenceTable<'a> {
|
|||
})
|
||||
.build();
|
||||
|
||||
let projection = {
|
||||
let b = TyBuilder::subst_for_def(self.db, fn_once_trait, None);
|
||||
if b.remaining() != 2 {
|
||||
return None;
|
||||
}
|
||||
let fn_once_subst = b.push(ty.clone()).push(arg_ty).build();
|
||||
let b = TyBuilder::trait_ref(self.db, fn_once_trait);
|
||||
if b.remaining() != 2 {
|
||||
return None;
|
||||
}
|
||||
let mut trait_ref = b.push(ty.clone()).push(arg_ty).build();
|
||||
|
||||
TyBuilder::assoc_type_projection(self.db, output_assoc_type, Some(fn_once_subst))
|
||||
.build()
|
||||
let projection = {
|
||||
TyBuilder::assoc_type_projection(
|
||||
self.db,
|
||||
output_assoc_type,
|
||||
Some(trait_ref.substitution.clone()),
|
||||
)
|
||||
.build()
|
||||
};
|
||||
|
||||
let trait_env = self.trait_env.env.clone();
|
||||
let mut trait_ref = projection.trait_ref(self.db);
|
||||
let obligation = InEnvironment {
|
||||
goal: trait_ref.clone().cast(Interner),
|
||||
environment: trait_env.clone(),
|
||||
|
|
|
@ -570,6 +570,10 @@ impl CallableSig {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn abi(&self) -> FnAbi {
|
||||
self.abi
|
||||
}
|
||||
|
||||
pub fn params(&self) -> &[Ty] {
|
||||
&self.params_and_return[0..self.params_and_return.len() - 1]
|
||||
}
|
||||
|
@ -892,20 +896,16 @@ where
|
|||
Canonical { value, binders: chalk_ir::CanonicalVarKinds::from_iter(Interner, kinds) }
|
||||
}
|
||||
|
||||
pub fn callable_sig_from_fnonce(
|
||||
mut self_ty: &Ty,
|
||||
env: Arc<TraitEnvironment>,
|
||||
pub fn callable_sig_from_fn_trait(
|
||||
self_ty: &Ty,
|
||||
trait_env: Arc<TraitEnvironment>,
|
||||
db: &dyn HirDatabase,
|
||||
) -> Option<CallableSig> {
|
||||
if let Some((ty, _, _)) = self_ty.as_reference() {
|
||||
// This will happen when it implements fn or fn mut, since we add a autoborrow adjustment
|
||||
self_ty = ty;
|
||||
}
|
||||
let krate = env.krate;
|
||||
) -> Option<(FnTrait, CallableSig)> {
|
||||
let krate = trait_env.krate;
|
||||
let fn_once_trait = FnTrait::FnOnce.get_id(db, krate)?;
|
||||
let output_assoc_type = db.trait_data(fn_once_trait).associated_type_by_name(&name![Output])?;
|
||||
|
||||
let mut table = InferenceTable::new(db, env);
|
||||
let mut table = InferenceTable::new(db, trait_env.clone());
|
||||
let b = TyBuilder::trait_ref(db, fn_once_trait);
|
||||
if b.remaining() != 2 {
|
||||
return None;
|
||||
|
@ -915,23 +915,56 @@ pub fn callable_sig_from_fnonce(
|
|||
// - Self: FnOnce<?args_ty>
|
||||
// - <Self as FnOnce<?args_ty>>::Output == ?ret_ty
|
||||
let args_ty = table.new_type_var();
|
||||
let trait_ref = b.push(self_ty.clone()).push(args_ty.clone()).build();
|
||||
let mut trait_ref = b.push(self_ty.clone()).push(args_ty.clone()).build();
|
||||
let projection = TyBuilder::assoc_type_projection(
|
||||
db,
|
||||
output_assoc_type,
|
||||
Some(trait_ref.substitution.clone()),
|
||||
)
|
||||
.build();
|
||||
table.register_obligation(trait_ref.cast(Interner));
|
||||
let ret_ty = table.normalize_projection_ty(projection);
|
||||
|
||||
let ret_ty = table.resolve_completely(ret_ty);
|
||||
let args_ty = table.resolve_completely(args_ty);
|
||||
let block = trait_env.block;
|
||||
let trait_env = trait_env.env.clone();
|
||||
let obligation =
|
||||
InEnvironment { goal: trait_ref.clone().cast(Interner), environment: trait_env.clone() };
|
||||
let canonical = table.canonicalize(obligation.clone());
|
||||
if db.trait_solve(krate, block, canonical.cast(Interner)).is_some() {
|
||||
table.register_obligation(obligation.goal);
|
||||
let return_ty = table.normalize_projection_ty(projection);
|
||||
for fn_x in [FnTrait::Fn, FnTrait::FnMut, FnTrait::FnOnce] {
|
||||
let fn_x_trait = fn_x.get_id(db, krate)?;
|
||||
trait_ref.trait_id = to_chalk_trait_id(fn_x_trait);
|
||||
let obligation: chalk_ir::InEnvironment<chalk_ir::Goal<Interner>> = InEnvironment {
|
||||
goal: trait_ref.clone().cast(Interner),
|
||||
environment: trait_env.clone(),
|
||||
};
|
||||
let canonical = table.canonicalize(obligation.clone());
|
||||
if db.trait_solve(krate, block, canonical.cast(Interner)).is_some() {
|
||||
let ret_ty = table.resolve_completely(return_ty);
|
||||
let args_ty = table.resolve_completely(args_ty);
|
||||
let params = args_ty
|
||||
.as_tuple()?
|
||||
.iter(Interner)
|
||||
.map(|it| it.assert_ty_ref(Interner))
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let params =
|
||||
args_ty.as_tuple()?.iter(Interner).map(|it| it.assert_ty_ref(Interner)).cloned().collect();
|
||||
|
||||
Some(CallableSig::from_params_and_return(params, ret_ty, false, Safety::Safe, FnAbi::RustCall))
|
||||
return Some((
|
||||
fn_x,
|
||||
CallableSig::from_params_and_return(
|
||||
params,
|
||||
ret_ty,
|
||||
false,
|
||||
Safety::Safe,
|
||||
FnAbi::RustCall,
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
unreachable!("It should at least implement FnOnce at this point");
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
struct PlaceholderCollector<'db> {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! Trait solving using Chalk.
|
||||
|
||||
use core::fmt;
|
||||
use std::env::var;
|
||||
|
||||
use chalk_ir::{fold::TypeFoldable, DebruijnIndex, GoalData};
|
||||
|
@ -209,7 +210,25 @@ pub enum FnTrait {
|
|||
Fn,
|
||||
}
|
||||
|
||||
impl fmt::Display for FnTrait {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
FnTrait::FnOnce => write!(f, "FnOnce"),
|
||||
FnTrait::FnMut => write!(f, "FnMut"),
|
||||
FnTrait::Fn => write!(f, "Fn"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FnTrait {
|
||||
pub const fn function_name(&self) -> &'static str {
|
||||
match self {
|
||||
FnTrait::FnOnce => "call_once",
|
||||
FnTrait::FnMut => "call_mut",
|
||||
FnTrait::Fn => "call",
|
||||
}
|
||||
}
|
||||
|
||||
const fn lang_item(self) -> LangItem {
|
||||
match self {
|
||||
FnTrait::FnOnce => LangItem::FnOnce,
|
||||
|
|
|
@ -35,7 +35,7 @@ pub mod term_search;
|
|||
|
||||
mod display;
|
||||
|
||||
use std::{iter, mem::discriminant, ops::ControlFlow};
|
||||
use std::{mem::discriminant, ops::ControlFlow};
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use base_db::{CrateDisplayName, CrateId, CrateOrigin, FileId};
|
||||
|
@ -52,7 +52,6 @@ use hir_def::{
|
|||
path::ImportAlias,
|
||||
per_ns::PerNs,
|
||||
resolver::{HasResolver, Resolver},
|
||||
src::HasSource as _,
|
||||
AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, CrateRootModuleId, DefWithBodyId,
|
||||
EnumId, EnumVariantId, ExternCrateId, FunctionId, GenericDefId, GenericParamId, HasModule,
|
||||
ImplId, InTypeConstId, ItemContainerId, LifetimeParamId, LocalFieldId, Lookup, MacroExpander,
|
||||
|
@ -141,7 +140,7 @@ pub use {
|
|||
display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite},
|
||||
layout::LayoutError,
|
||||
mir::{MirEvalError, MirLowerError},
|
||||
PointerCast, Safety,
|
||||
FnAbi, PointerCast, Safety,
|
||||
},
|
||||
// FIXME: Properly encapsulate mir
|
||||
hir_ty::{mir, Interner as ChalkTyInterner},
|
||||
|
@ -1965,7 +1964,7 @@ impl Function {
|
|||
.enumerate()
|
||||
.map(|(idx, ty)| {
|
||||
let ty = Type { env: environment.clone(), ty: ty.clone() };
|
||||
Param { func: self, ty, idx }
|
||||
Param { func: Callee::Def(CallableDefId::FunctionId(self.id)), ty, idx }
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
@ -1991,7 +1990,7 @@ impl Function {
|
|||
.skip(skip)
|
||||
.map(|(idx, ty)| {
|
||||
let ty = Type { env: environment.clone(), ty: ty.clone() };
|
||||
Param { func: self, ty, idx }
|
||||
Param { func: Callee::Def(CallableDefId::FunctionId(self.id)), ty, idx }
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
@ -2037,7 +2036,7 @@ impl Function {
|
|||
.skip(skip)
|
||||
.map(|(idx, ty)| {
|
||||
let ty = Type { env: environment.clone(), ty: ty.clone() };
|
||||
Param { func: self, ty, idx }
|
||||
Param { func: Callee::Def(CallableDefId::FunctionId(self.id)), ty, idx }
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
@ -2167,17 +2166,24 @@ impl From<hir_ty::Mutability> for Access {
|
|||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct Param {
|
||||
func: Function,
|
||||
func: Callee,
|
||||
/// The index in parameter list, including self parameter.
|
||||
idx: usize,
|
||||
ty: Type,
|
||||
}
|
||||
|
||||
impl Param {
|
||||
pub fn parent_fn(&self) -> Function {
|
||||
self.func
|
||||
pub fn parent_fn(&self) -> Option<Function> {
|
||||
match self.func {
|
||||
Callee::Def(CallableDefId::FunctionId(f)) => Some(f.into()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn parent_closure(&self) -> Option<Closure> {
|
||||
// self.func.as_ref().right().cloned()
|
||||
// }
|
||||
|
||||
pub fn index(&self) -> usize {
|
||||
self.idx
|
||||
}
|
||||
|
@ -2191,7 +2197,11 @@ impl Param {
|
|||
}
|
||||
|
||||
pub fn as_local(&self, db: &dyn HirDatabase) -> Option<Local> {
|
||||
let parent = DefWithBodyId::FunctionId(self.func.into());
|
||||
let parent = match self.func {
|
||||
Callee::Def(CallableDefId::FunctionId(it)) => DefWithBodyId::FunctionId(it),
|
||||
Callee::Closure(closure, _) => db.lookup_intern_closure(closure.into()).0,
|
||||
_ => return None,
|
||||
};
|
||||
let body = db.body(parent);
|
||||
if let Some(self_param) = body.self_param.filter(|_| self.idx == 0) {
|
||||
Some(Local { parent, binding_id: self_param })
|
||||
|
@ -2205,18 +2215,45 @@ impl Param {
|
|||
}
|
||||
|
||||
pub fn pattern_source(&self, db: &dyn HirDatabase) -> Option<ast::Pat> {
|
||||
self.source(db).and_then(|p| p.value.pat())
|
||||
self.source(db).and_then(|p| p.value.right()?.pat())
|
||||
}
|
||||
|
||||
pub fn source(&self, db: &dyn HirDatabase) -> Option<InFile<ast::Param>> {
|
||||
let InFile { file_id, value } = self.func.source(db)?;
|
||||
let params = value.param_list()?;
|
||||
if params.self_param().is_some() {
|
||||
params.params().nth(self.idx.checked_sub(params.self_param().is_some() as usize)?)
|
||||
} else {
|
||||
params.params().nth(self.idx)
|
||||
pub fn source(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
) -> Option<InFile<Either<ast::SelfParam, ast::Param>>> {
|
||||
match self.func {
|
||||
Callee::Def(CallableDefId::FunctionId(func)) => {
|
||||
let InFile { file_id, value } = Function { id: func }.source(db)?;
|
||||
let params = value.param_list()?;
|
||||
if let Some(self_param) = params.self_param() {
|
||||
if let Some(idx) = self.idx.checked_sub(1) {
|
||||
params.params().nth(idx).map(Either::Right)
|
||||
} else {
|
||||
Some(Either::Left(self_param))
|
||||
}
|
||||
} else {
|
||||
params.params().nth(self.idx).map(Either::Right)
|
||||
}
|
||||
.map(|value| InFile { file_id, value })
|
||||
}
|
||||
Callee::Closure(closure, _) => {
|
||||
let InternedClosure(owner, expr_id) = db.lookup_intern_closure(closure.into());
|
||||
let (_, source_map) = db.body_with_source_map(owner);
|
||||
let ast @ InFile { file_id, value } = source_map.expr_syntax(expr_id).ok()?;
|
||||
let root = db.parse_or_expand(file_id);
|
||||
match value.to_node(&root) {
|
||||
ast::Expr::ClosureExpr(it) => it
|
||||
.param_list()?
|
||||
.params()
|
||||
.nth(self.idx)
|
||||
.map(Either::Right)
|
||||
.map(|value| InFile { file_id: ast.file_id, value }),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
.map(|value| InFile { file_id, value })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3372,34 +3409,21 @@ impl BuiltinAttr {
|
|||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ToolModule {
|
||||
krate: Option<CrateId>,
|
||||
krate: CrateId,
|
||||
idx: u32,
|
||||
}
|
||||
|
||||
impl ToolModule {
|
||||
// FIXME: consider crates\hir_def\src\nameres\attr_resolution.rs?
|
||||
pub(crate) fn by_name(db: &dyn HirDatabase, krate: Crate, name: &str) -> Option<Self> {
|
||||
if let builtin @ Some(_) = Self::builtin(name) {
|
||||
return builtin;
|
||||
}
|
||||
let krate = krate.id;
|
||||
let idx =
|
||||
db.crate_def_map(krate.id).registered_tools().iter().position(|it| it == name)? as u32;
|
||||
Some(ToolModule { krate: Some(krate.id), idx })
|
||||
}
|
||||
|
||||
fn builtin(name: &str) -> Option<Self> {
|
||||
hir_def::attr::builtin::TOOL_MODULES
|
||||
.iter()
|
||||
.position(|&tool| tool == name)
|
||||
.map(|idx| ToolModule { krate: None, idx: idx as u32 })
|
||||
db.crate_def_map(krate).registered_tools().iter().position(|it| it == name)? as u32;
|
||||
Some(ToolModule { krate, idx })
|
||||
}
|
||||
|
||||
pub fn name(&self, db: &dyn HirDatabase) -> SmolStr {
|
||||
// FIXME: Return a `Name` here
|
||||
match self.krate {
|
||||
Some(krate) => db.crate_def_map(krate).registered_tools()[self.idx as usize].clone(),
|
||||
None => SmolStr::new(hir_def::attr::builtin::TOOL_MODULES[self.idx as usize]),
|
||||
}
|
||||
db.crate_def_map(self.krate).registered_tools()[self.idx as usize].clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4292,27 +4316,37 @@ impl Type {
|
|||
}
|
||||
|
||||
pub fn as_callable(&self, db: &dyn HirDatabase) -> Option<Callable> {
|
||||
let mut the_ty = &self.ty;
|
||||
let callee = match self.ty.kind(Interner) {
|
||||
TyKind::Ref(_, _, ty) if ty.as_closure().is_some() => {
|
||||
the_ty = ty;
|
||||
Callee::Closure(ty.as_closure().unwrap())
|
||||
}
|
||||
TyKind::Closure(id, _) => Callee::Closure(*id),
|
||||
TyKind::Closure(id, subst) => Callee::Closure(*id, subst.clone()),
|
||||
TyKind::Function(_) => Callee::FnPtr,
|
||||
TyKind::FnDef(..) => Callee::Def(self.ty.callable_def(db)?),
|
||||
_ => {
|
||||
let sig = hir_ty::callable_sig_from_fnonce(&self.ty, self.env.clone(), db)?;
|
||||
kind => {
|
||||
// This will happen when it implements fn or fn mut, since we add an autoborrow adjustment
|
||||
let (ty, kind) = if let TyKind::Ref(_, _, ty) = kind {
|
||||
(ty, ty.kind(Interner))
|
||||
} else {
|
||||
(&self.ty, kind)
|
||||
};
|
||||
if let TyKind::Closure(closure, subst) = kind {
|
||||
let sig = ty.callable_sig(db)?;
|
||||
return Some(Callable {
|
||||
ty: self.clone(),
|
||||
sig,
|
||||
callee: Callee::Closure(*closure, subst.clone()),
|
||||
is_bound_method: false,
|
||||
});
|
||||
}
|
||||
let (fn_trait, sig) = hir_ty::callable_sig_from_fn_trait(ty, self.env.clone(), db)?;
|
||||
return Some(Callable {
|
||||
ty: self.clone(),
|
||||
sig,
|
||||
callee: Callee::Other,
|
||||
callee: Callee::FnImpl(fn_trait),
|
||||
is_bound_method: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let sig = the_ty.callable_sig(db)?;
|
||||
let sig = self.ty.callable_sig(db)?;
|
||||
Some(Callable { ty: self.clone(), sig, callee, is_bound_method: false })
|
||||
}
|
||||
|
||||
|
@ -4929,37 +4963,39 @@ pub struct Callable {
|
|||
sig: CallableSig,
|
||||
callee: Callee,
|
||||
/// Whether this is a method that was called with method call syntax.
|
||||
pub(crate) is_bound_method: bool,
|
||||
is_bound_method: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
enum Callee {
|
||||
Def(CallableDefId),
|
||||
Closure(ClosureId),
|
||||
Closure(ClosureId, Substitution),
|
||||
FnPtr,
|
||||
Other,
|
||||
FnImpl(FnTrait),
|
||||
}
|
||||
|
||||
pub enum CallableKind {
|
||||
Function(Function),
|
||||
TupleStruct(Struct),
|
||||
TupleEnumVariant(Variant),
|
||||
Closure,
|
||||
Closure(Closure),
|
||||
FnPtr,
|
||||
/// Some other type that implements `FnOnce`.
|
||||
Other,
|
||||
FnImpl(FnTrait),
|
||||
}
|
||||
|
||||
impl Callable {
|
||||
pub fn kind(&self) -> CallableKind {
|
||||
use Callee::*;
|
||||
match self.callee {
|
||||
Def(CallableDefId::FunctionId(it)) => CallableKind::Function(it.into()),
|
||||
Def(CallableDefId::StructId(it)) => CallableKind::TupleStruct(it.into()),
|
||||
Def(CallableDefId::EnumVariantId(it)) => CallableKind::TupleEnumVariant(it.into()),
|
||||
Closure(_) => CallableKind::Closure,
|
||||
FnPtr => CallableKind::FnPtr,
|
||||
Other => CallableKind::Other,
|
||||
Callee::Def(CallableDefId::FunctionId(it)) => CallableKind::Function(it.into()),
|
||||
Callee::Def(CallableDefId::StructId(it)) => CallableKind::TupleStruct(it.into()),
|
||||
Callee::Def(CallableDefId::EnumVariantId(it)) => {
|
||||
CallableKind::TupleEnumVariant(it.into())
|
||||
}
|
||||
Callee::Closure(id, ref subst) => {
|
||||
CallableKind::Closure(Closure { id, subst: subst.clone() })
|
||||
}
|
||||
Callee::FnPtr => CallableKind::FnPtr,
|
||||
Callee::FnImpl(fn_) => CallableKind::FnImpl(fn_),
|
||||
}
|
||||
}
|
||||
pub fn receiver_param(&self, db: &dyn HirDatabase) -> Option<(SelfParam, Type)> {
|
||||
|
@ -4973,43 +5009,15 @@ impl Callable {
|
|||
pub fn n_params(&self) -> usize {
|
||||
self.sig.params().len() - if self.is_bound_method { 1 } else { 0 }
|
||||
}
|
||||
pub fn params(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
) -> Vec<(Option<Either<ast::SelfParam, ast::Pat>>, Type)> {
|
||||
let types = self
|
||||
.sig
|
||||
pub fn params(&self) -> Vec<Param> {
|
||||
self.sig
|
||||
.params()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(if self.is_bound_method { 1 } else { 0 })
|
||||
.map(|ty| self.ty.derived(ty.clone()));
|
||||
let map_param = |it: ast::Param| it.pat().map(Either::Right);
|
||||
let patterns = match self.callee {
|
||||
Callee::Def(CallableDefId::FunctionId(func)) => {
|
||||
let src = func.lookup(db.upcast()).source(db.upcast());
|
||||
src.value.param_list().map(|param_list| {
|
||||
param_list
|
||||
.self_param()
|
||||
.map(|it| Some(Either::Left(it)))
|
||||
.filter(|_| !self.is_bound_method)
|
||||
.into_iter()
|
||||
.chain(param_list.params().map(map_param))
|
||||
})
|
||||
}
|
||||
Callee::Closure(closure_id) => match closure_source(db, closure_id) {
|
||||
Some(src) => src.param_list().map(|param_list| {
|
||||
param_list
|
||||
.self_param()
|
||||
.map(|it| Some(Either::Left(it)))
|
||||
.filter(|_| !self.is_bound_method)
|
||||
.into_iter()
|
||||
.chain(param_list.params().map(map_param))
|
||||
}),
|
||||
None => None,
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
patterns.into_iter().flatten().chain(iter::repeat(None)).zip(types).collect()
|
||||
.map(|(idx, ty)| (idx, self.ty.derived(ty.clone())))
|
||||
.map(|(idx, ty)| Param { func: self.callee.clone(), idx, ty })
|
||||
.collect()
|
||||
}
|
||||
pub fn return_type(&self) -> Type {
|
||||
self.ty.derived(self.sig.ret().clone())
|
||||
|
@ -5017,17 +5025,9 @@ impl Callable {
|
|||
pub fn sig(&self) -> &CallableSig {
|
||||
&self.sig
|
||||
}
|
||||
}
|
||||
|
||||
fn closure_source(db: &dyn HirDatabase, closure: ClosureId) -> Option<ast::ClosureExpr> {
|
||||
let InternedClosure(owner, expr_id) = db.lookup_intern_closure(closure.into());
|
||||
let (_, source_map) = db.body_with_source_map(owner);
|
||||
let ast = source_map.expr_syntax(expr_id).ok()?;
|
||||
let root = ast.file_syntax(db.upcast());
|
||||
let expr = ast.value.to_node(&root);
|
||||
match expr {
|
||||
ast::Expr::ClosureExpr(it) => Some(it),
|
||||
_ => None,
|
||||
pub fn ty(&self) -> &Type {
|
||||
&self.ty
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -307,7 +307,8 @@ impl SourceAnalyzer {
|
|||
db: &dyn HirDatabase,
|
||||
call: &ast::Expr,
|
||||
) -> Option<Callable> {
|
||||
self.type_of_expr(db, &call.clone())?.0.as_callable(db)
|
||||
let (orig, adjusted) = self.type_of_expr(db, &call.clone())?;
|
||||
adjusted.unwrap_or(orig).as_callable(db)
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_field(
|
||||
|
|
|
@ -114,10 +114,10 @@ pub(crate) fn replace_with_eager_method(acc: &mut Assists, ctx: &AssistContext<'
|
|||
let callable = ctx.sema.resolve_method_call_as_callable(&call)?;
|
||||
let (_, receiver_ty) = callable.receiver_param(ctx.sema.db)?;
|
||||
let n_params = callable.n_params() + 1;
|
||||
let params = callable.params(ctx.sema.db);
|
||||
let params = callable.params();
|
||||
|
||||
// FIXME: Check that the arg is of the form `() -> T`
|
||||
if !params.first()?.1.impls_fnonce(ctx.sema.db) {
|
||||
if !params.first()?.ty().impls_fnonce(ctx.sema.db) {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
|
|
@ -253,11 +253,8 @@ fn from_param(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<St
|
|||
};
|
||||
|
||||
let (idx, _) = arg_list.args().find_position(|it| it == expr).unwrap();
|
||||
let (pat, _) = func.params(sema.db).into_iter().nth(idx)?;
|
||||
let pat = match pat? {
|
||||
either::Either::Right(pat) => pat,
|
||||
_ => return None,
|
||||
};
|
||||
let param = func.params().into_iter().nth(idx)?;
|
||||
let pat = param.source(sema.db)?.value.right()?.pat()?;
|
||||
let name = var_name_from_pat(&pat)?;
|
||||
normalize(&name.to_string())
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! This module provides functionality for querying callable information about a token.
|
||||
|
||||
use either::Either;
|
||||
use hir::{Semantics, Type};
|
||||
use hir::{InFile, Semantics, Type};
|
||||
use parser::T;
|
||||
use syntax::{
|
||||
ast::{self, HasArgList, HasName},
|
||||
|
@ -13,7 +13,7 @@ use crate::RootDatabase;
|
|||
#[derive(Debug)]
|
||||
pub struct ActiveParameter {
|
||||
pub ty: Type,
|
||||
pub pat: Option<Either<ast::SelfParam, ast::Pat>>,
|
||||
pub src: Option<InFile<Either<ast::SelfParam, ast::Param>>>,
|
||||
}
|
||||
|
||||
impl ActiveParameter {
|
||||
|
@ -22,18 +22,18 @@ impl ActiveParameter {
|
|||
let (signature, active_parameter) = callable_for_token(sema, token)?;
|
||||
|
||||
let idx = active_parameter?;
|
||||
let mut params = signature.params(sema.db);
|
||||
let mut params = signature.params();
|
||||
if idx >= params.len() {
|
||||
cov_mark::hit!(too_many_arguments);
|
||||
return None;
|
||||
}
|
||||
let (pat, ty) = params.swap_remove(idx);
|
||||
Some(ActiveParameter { ty, pat })
|
||||
let param = params.swap_remove(idx);
|
||||
Some(ActiveParameter { ty: param.ty().clone(), src: param.source(sema.db) })
|
||||
}
|
||||
|
||||
pub fn ident(&self) -> Option<ast::Name> {
|
||||
self.pat.as_ref().and_then(|param| match param {
|
||||
Either::Right(ast::Pat::IdentPat(ident)) => ident.name(),
|
||||
self.src.as_ref().and_then(|param| match param.value.as_ref().right()?.pat()? {
|
||||
ast::Pat::IdentPat(ident) => ident.name(),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
@ -60,10 +60,7 @@ pub fn callable_for_node(
|
|||
token: &SyntaxToken,
|
||||
) -> Option<(hir::Callable, Option<usize>)> {
|
||||
let callable = match calling_node {
|
||||
ast::CallableExpr::Call(call) => {
|
||||
let expr = call.expr()?;
|
||||
sema.type_of_expr(&expr)?.adjusted().as_callable(sema.db)
|
||||
}
|
||||
ast::CallableExpr::Call(call) => sema.resolve_expr_as_callable(&call.expr()?),
|
||||
ast::CallableExpr::MethodCall(call) => sema.resolve_method_call_as_callable(call),
|
||||
}?;
|
||||
let active_param = calling_node.arg_list().map(|arg_list| {
|
||||
|
|
|
@ -41,6 +41,7 @@ pub enum FormatSpecifier {
|
|||
Escape,
|
||||
}
|
||||
|
||||
// FIXME: Remove this, we can use rustc_format_parse instead
|
||||
pub fn lex_format_specifiers(
|
||||
string: &ast::String,
|
||||
mut callback: &mut dyn FnMut(TextRange, FormatSpecifier),
|
||||
|
|
|
@ -109,12 +109,12 @@ pub(crate) fn outgoing_calls(
|
|||
let expr = call.expr()?;
|
||||
let callable = sema.type_of_expr(&expr)?.original.as_callable(db)?;
|
||||
match callable.kind() {
|
||||
hir::CallableKind::Function(it) => {
|
||||
let range = expr.syntax().text_range();
|
||||
it.try_to_nav(db).zip(Some(range))
|
||||
}
|
||||
hir::CallableKind::Function(it) => it.try_to_nav(db),
|
||||
hir::CallableKind::TupleEnumVariant(it) => it.try_to_nav(db),
|
||||
hir::CallableKind::TupleStruct(it) => it.try_to_nav(db),
|
||||
_ => None,
|
||||
}
|
||||
.zip(Some(expr.syntax().text_range()))
|
||||
}
|
||||
ast::CallableExpr::MethodCall(expr) => {
|
||||
let range = expr.name_ref()?.syntax().text_range();
|
||||
|
|
|
@ -24,34 +24,29 @@ pub(super) fn hints(
|
|||
|
||||
let (callable, arg_list) = get_callable(sema, &expr)?;
|
||||
let hints = callable
|
||||
.params(sema.db)
|
||||
.params()
|
||||
.into_iter()
|
||||
.zip(arg_list.args())
|
||||
.filter_map(|((param, _ty), arg)| {
|
||||
.filter_map(|(p, arg)| {
|
||||
// Only annotate hints for expressions that exist in the original file
|
||||
let range = sema.original_range_opt(arg.syntax())?;
|
||||
let (param_name, name_syntax) = match param.as_ref()? {
|
||||
let source = p.source(sema.db)?;
|
||||
let (param_name, name_syntax) = match source.value.as_ref() {
|
||||
Either::Left(pat) => (pat.name()?, pat.name()),
|
||||
Either::Right(pat) => match pat {
|
||||
Either::Right(param) => match param.pat()? {
|
||||
ast::Pat::IdentPat(it) => (it.name()?, it.name()),
|
||||
_ => return None,
|
||||
},
|
||||
};
|
||||
// make sure the file is cached so we can map out of macros
|
||||
sema.parse_or_expand(source.file_id);
|
||||
Some((name_syntax, param_name, arg, range))
|
||||
})
|
||||
.filter(|(_, param_name, arg, _)| {
|
||||
!should_hide_param_name_hint(sema, &callable, ¶m_name.text(), arg)
|
||||
})
|
||||
.map(|(param, param_name, _, FileRange { range, .. })| {
|
||||
let mut linked_location = None;
|
||||
if let Some(name) = param {
|
||||
if let hir::CallableKind::Function(f) = callable.kind() {
|
||||
// assert the file is cached so we can map out of macros
|
||||
if sema.source(f).is_some() {
|
||||
linked_location = sema.original_range_opt(name.syntax());
|
||||
}
|
||||
}
|
||||
}
|
||||
let linked_location = param.and_then(|name| sema.original_range_opt(name.syntax()));
|
||||
|
||||
let colon = if config.render_colons { ":" } else { "" };
|
||||
let label =
|
||||
|
|
|
@ -201,7 +201,21 @@ fn signature_help_for_call(
|
|||
variant.name(db).display(db)
|
||||
);
|
||||
}
|
||||
hir::CallableKind::Closure | hir::CallableKind::FnPtr | hir::CallableKind::Other => (),
|
||||
hir::CallableKind::Closure(closure) => {
|
||||
let fn_trait = closure.fn_trait(db);
|
||||
format_to!(res.signature, "impl {fn_trait}")
|
||||
}
|
||||
hir::CallableKind::FnPtr => format_to!(res.signature, "fn"),
|
||||
hir::CallableKind::FnImpl(fn_trait) => match callable.ty().as_adt() {
|
||||
// FIXME: Render docs of the concrete trait impl function
|
||||
Some(adt) => format_to!(
|
||||
res.signature,
|
||||
"<{} as {fn_trait}>::{}",
|
||||
adt.name(db).display(db),
|
||||
fn_trait.function_name()
|
||||
),
|
||||
None => format_to!(res.signature, "impl {fn_trait}"),
|
||||
},
|
||||
}
|
||||
|
||||
res.signature.push('(');
|
||||
|
@ -210,12 +224,15 @@ fn signature_help_for_call(
|
|||
format_to!(res.signature, "{}", self_param.display(db))
|
||||
}
|
||||
let mut buf = String::new();
|
||||
for (idx, (pat, ty)) in callable.params(db).into_iter().enumerate() {
|
||||
for (idx, p) in callable.params().into_iter().enumerate() {
|
||||
buf.clear();
|
||||
if let Some(pat) = pat {
|
||||
match pat {
|
||||
Either::Left(_self) => format_to!(buf, "self: "),
|
||||
Either::Right(pat) => format_to!(buf, "{}: ", pat),
|
||||
if let Some(param) = p.source(sema.db) {
|
||||
match param.value {
|
||||
Either::Right(param) => match param.pat() {
|
||||
Some(pat) => format_to!(buf, "{}: ", pat),
|
||||
None => format_to!(buf, "?: "),
|
||||
},
|
||||
Either::Left(_) => format_to!(buf, "self: "),
|
||||
}
|
||||
}
|
||||
// APITs (argument position `impl Trait`s) are inferred as {unknown} as the user is
|
||||
|
@ -223,9 +240,9 @@ fn signature_help_for_call(
|
|||
// In that case, fall back to render definitions of the respective parameters.
|
||||
// This is overly conservative: we do not substitute known type vars
|
||||
// (see FIXME in tests::impl_trait) and falling back on any unknowns.
|
||||
match (ty.contains_unknown(), fn_params.as_deref()) {
|
||||
match (p.ty().contains_unknown(), fn_params.as_deref()) {
|
||||
(true, Some(fn_params)) => format_to!(buf, "{}", fn_params[idx].ty().display(db)),
|
||||
_ => format_to!(buf, "{}", ty.display(db)),
|
||||
_ => format_to!(buf, "{}", p.ty().display(db)),
|
||||
}
|
||||
res.push_call_param(&buf);
|
||||
}
|
||||
|
@ -242,9 +259,9 @@ fn signature_help_for_call(
|
|||
render(func.ret_type(db))
|
||||
}
|
||||
hir::CallableKind::Function(_)
|
||||
| hir::CallableKind::Closure
|
||||
| hir::CallableKind::Closure(_)
|
||||
| hir::CallableKind::FnPtr
|
||||
| hir::CallableKind::Other => render(callable.return_type()),
|
||||
| hir::CallableKind::FnImpl(_) => render(callable.return_type()),
|
||||
hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {}
|
||||
}
|
||||
Some(res)
|
||||
|
@ -1345,15 +1362,43 @@ fn test() { S.foo($0); }
|
|||
r#"
|
||||
struct S;
|
||||
fn foo(s: S) -> i32 { 92 }
|
||||
fn main() {
|
||||
let _move = S;
|
||||
(|s| {{_move}; foo(s)})($0)
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
impl FnOnce(s: S) -> i32
|
||||
^^^^
|
||||
"#]],
|
||||
);
|
||||
check(
|
||||
r#"
|
||||
struct S;
|
||||
fn foo(s: S) -> i32 { 92 }
|
||||
fn main() {
|
||||
(|s| foo(s))($0)
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
(s: S) -> i32
|
||||
^^^^
|
||||
impl Fn(s: S) -> i32
|
||||
^^^^
|
||||
"#]],
|
||||
)
|
||||
);
|
||||
check(
|
||||
r#"
|
||||
struct S;
|
||||
fn foo(s: S) -> i32 { 92 }
|
||||
fn main() {
|
||||
let mut mutate = 0;
|
||||
(|s| { mutate = 1; foo(s) })($0)
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
impl FnMut(s: S) -> i32
|
||||
^^^^
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1383,12 +1428,81 @@ fn main(f: fn(i32, f64) -> char) {
|
|||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
(i32, f64) -> char
|
||||
--- ^^^
|
||||
fn(i32, f64) -> char
|
||||
--- ^^^
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_info_for_fn_impl() {
|
||||
check(
|
||||
r#"
|
||||
struct S;
|
||||
impl core::ops::FnOnce<(i32, f64)> for S {
|
||||
type Output = char;
|
||||
}
|
||||
impl core::ops::FnMut<(i32, f64)> for S {}
|
||||
impl core::ops::Fn<(i32, f64)> for S {}
|
||||
fn main() {
|
||||
S($0);
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
<S as Fn>::call(i32, f64) -> char
|
||||
^^^ ---
|
||||
"#]],
|
||||
);
|
||||
check(
|
||||
r#"
|
||||
struct S;
|
||||
impl core::ops::FnOnce<(i32, f64)> for S {
|
||||
type Output = char;
|
||||
}
|
||||
impl core::ops::FnMut<(i32, f64)> for S {}
|
||||
impl core::ops::Fn<(i32, f64)> for S {}
|
||||
fn main() {
|
||||
S(1, $0);
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
<S as Fn>::call(i32, f64) -> char
|
||||
--- ^^^
|
||||
"#]],
|
||||
);
|
||||
check(
|
||||
r#"
|
||||
struct S;
|
||||
impl core::ops::FnOnce<(i32, f64)> for S {
|
||||
type Output = char;
|
||||
}
|
||||
impl core::ops::FnOnce<(char, char)> for S {
|
||||
type Output = f64;
|
||||
}
|
||||
fn main() {
|
||||
S($0);
|
||||
}
|
||||
"#,
|
||||
expect![""],
|
||||
);
|
||||
check(
|
||||
r#"
|
||||
struct S;
|
||||
impl core::ops::FnOnce<(i32, f64)> for S {
|
||||
type Output = char;
|
||||
}
|
||||
impl core::ops::FnOnce<(char, char)> for S {
|
||||
type Output = f64;
|
||||
}
|
||||
fn main() {
|
||||
// FIXME: The ide layer loses the calling info here so we get an ambiguous trait solve result
|
||||
S(0i32, $0);
|
||||
}
|
||||
"#,
|
||||
expect![""],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_info_for_unclosed_call() {
|
||||
check(
|
||||
|
@ -1794,19 +1908,19 @@ fn f<F: FnOnce(u8, u16) -> i32>(f: F) {
|
|||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
(u8, u16) -> i32
|
||||
^^ ---
|
||||
impl FnOnce(u8, u16) -> i32
|
||||
^^ ---
|
||||
"#]],
|
||||
);
|
||||
check(
|
||||
r#"
|
||||
fn f<T, F: FnOnce(&T, u16) -> &T>(f: F) {
|
||||
fn f<T, F: FnMut(&T, u16) -> &T>(f: F) {
|
||||
f($0)
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
(&T, u16) -> &T
|
||||
^^ ---
|
||||
impl FnMut(&T, u16) -> &T
|
||||
^^ ---
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
@ -1826,7 +1940,7 @@ fn take<C, Error>(
|
|||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
() -> i32
|
||||
impl Fn() -> i32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,8 +9,9 @@ pub(super) fn highlight_escape_string<T: IsString>(
|
|||
string: &T,
|
||||
start: TextSize,
|
||||
) {
|
||||
let text = string.text();
|
||||
string.escaped_char_ranges(&mut |piece_range, char| {
|
||||
if string.text()[piece_range.start().into()..].starts_with('\\') {
|
||||
if text[piece_range.start().into()..].starts_with('\\') {
|
||||
let highlight = match char {
|
||||
Ok(_) => HlTag::EscapeSequence,
|
||||
Err(_) => HlTag::InvalidEscapeSequence,
|
||||
|
@ -33,17 +34,15 @@ pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char, start:
|
|||
}
|
||||
|
||||
let text = char.text();
|
||||
if !text.starts_with('\'') || !text.ends_with('\'') {
|
||||
let Some(text) = text
|
||||
.strip_prefix('\'')
|
||||
.and_then(|it| it.strip_suffix('\''))
|
||||
.filter(|it| it.starts_with('\\'))
|
||||
else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let text = &text[1..text.len() - 1];
|
||||
if !text.starts_with('\\') {
|
||||
return;
|
||||
}
|
||||
|
||||
let range =
|
||||
TextRange::new(start + TextSize::from(1), start + TextSize::from(text.len() as u32 + 1));
|
||||
let range = TextRange::at(start + TextSize::from(1), TextSize::from(text.len() as u32));
|
||||
stack.add(HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None })
|
||||
}
|
||||
|
||||
|
@ -54,16 +53,14 @@ pub(super) fn highlight_escape_byte(stack: &mut Highlights, byte: &Byte, start:
|
|||
}
|
||||
|
||||
let text = byte.text();
|
||||
if !text.starts_with("b'") || !text.ends_with('\'') {
|
||||
let Some(text) = text
|
||||
.strip_prefix("b'")
|
||||
.and_then(|it| it.strip_suffix('\''))
|
||||
.filter(|it| it.starts_with('\\'))
|
||||
else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let text = &text[2..text.len() - 1];
|
||||
if !text.starts_with('\\') {
|
||||
return;
|
||||
}
|
||||
|
||||
let range =
|
||||
TextRange::new(start + TextSize::from(2), start + TextSize::from(text.len() as u32 + 2));
|
||||
let range = TextRange::at(start + TextSize::from(2), TextSize::from(text.len() as u32));
|
||||
stack.add(HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None })
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ use std::{
|
|||
use rustc_lexer::unescape::{
|
||||
unescape_byte, unescape_char, unescape_mixed, unescape_unicode, EscapeError, MixedUnit, Mode,
|
||||
};
|
||||
use stdx::always;
|
||||
|
||||
use crate::{
|
||||
ast::{self, AstToken},
|
||||
|
@ -181,25 +182,25 @@ pub trait IsString: AstToken {
|
|||
self.quote_offsets().map(|it| it.quotes.1)
|
||||
}
|
||||
fn escaped_char_ranges(&self, cb: &mut dyn FnMut(TextRange, Result<char, EscapeError>)) {
|
||||
let text_range_no_quotes = match self.text_range_between_quotes() {
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
};
|
||||
let Some(text_range_no_quotes) = self.text_range_between_quotes() else { return };
|
||||
|
||||
let start = self.syntax().text_range().start();
|
||||
let text = &self.text()[text_range_no_quotes - start];
|
||||
let offset = text_range_no_quotes.start() - start;
|
||||
|
||||
unescape_unicode(text, Self::MODE, &mut |range, unescaped_char| {
|
||||
let text_range =
|
||||
TextRange::new(range.start.try_into().unwrap(), range.end.try_into().unwrap());
|
||||
cb(text_range + offset, unescaped_char);
|
||||
if let Some((s, e)) = range.start.try_into().ok().zip(range.end.try_into().ok()) {
|
||||
cb(TextRange::new(s, e) + offset, unescaped_char);
|
||||
}
|
||||
});
|
||||
}
|
||||
fn map_range_up(&self, range: TextRange) -> Option<TextRange> {
|
||||
let contents_range = self.text_range_between_quotes()?;
|
||||
assert!(TextRange::up_to(contents_range.len()).contains_range(range));
|
||||
Some(range + contents_range.start())
|
||||
if always!(TextRange::up_to(contents_range.len()).contains_range(range)) {
|
||||
Some(range + contents_range.start())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue