mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-26 03:45:04 +00:00
commit
50b641ea4c
226 changed files with 7530 additions and 2880 deletions
44
Cargo.lock
generated
44
Cargo.lock
generated
|
@ -84,6 +84,12 @@ dependencies = [
|
|||
"vfs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
|
@ -509,6 +515,7 @@ dependencies = [
|
|||
"base-db",
|
||||
"cfg",
|
||||
"either",
|
||||
"expect-test",
|
||||
"hir-def",
|
||||
"hir-expand",
|
||||
"hir-ty",
|
||||
|
@ -519,6 +526,9 @@ dependencies = [
|
|||
"span",
|
||||
"stdx",
|
||||
"syntax",
|
||||
"syntax-bridge",
|
||||
"test-fixture",
|
||||
"test-utils",
|
||||
"tracing",
|
||||
"triomphe",
|
||||
"tt",
|
||||
|
@ -1492,9 +1502,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ra-ap-rustc_abi"
|
||||
version = "0.80.0"
|
||||
version = "0.85.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613760a3071b25a67a8d7bc97b37c7fd4722562e9479137b83ae9cf8f8c1601a"
|
||||
checksum = "af462c3a2d524b84a51b6848b439787f01b35c6c1086d3e3086a5f5eea92ed9a"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"ra-ap-rustc_index",
|
||||
|
@ -1503,20 +1513,19 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ra-ap-rustc_index"
|
||||
version = "0.80.0"
|
||||
version = "0.85.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b2bc6b4ecede8ff28295041e22c2e66853f8e0125990c05135bad3c30bad12c"
|
||||
checksum = "be6bb8cb0ab78d94a222f1ffd3e87254cdfb57413382b8d6ebe26a85482f99d1"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"ra-ap-rustc_index_macros",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ra-ap-rustc_index_macros"
|
||||
version = "0.80.0"
|
||||
version = "0.85.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2374a39fb2d92d0509178c2b442eadca3cc10e403ef9729a040c1855b08ff261"
|
||||
checksum = "c24b1641455b46e87435b7321219672077066e678963d239a4a2904732979b16"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1525,9 +1534,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ra-ap-rustc_lexer"
|
||||
version = "0.80.0"
|
||||
version = "0.85.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a2cf8e48b69af3ecc29ed3449892e8a999111d2f75212a78aa242e117cf1711"
|
||||
checksum = "94daa86974417981fed2f12bd8fb00158dfa6fee561152bed689278c846d0272"
|
||||
dependencies = [
|
||||
"unicode-properties",
|
||||
"unicode-xid",
|
||||
|
@ -1535,9 +1544,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ra-ap-rustc_parse_format"
|
||||
version = "0.80.0"
|
||||
version = "0.85.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d6f59a22b559263c5c42747ae362cf5d4fb272293fa119a4623f8ec288f9656"
|
||||
checksum = "fc07f6bd581746f358e39c4b6bfe8d455b3d6ad1a857821016d0d42eeb5e1e3e"
|
||||
dependencies = [
|
||||
"ra-ap-rustc_index",
|
||||
"ra-ap-rustc_lexer",
|
||||
|
@ -1545,9 +1554,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ra-ap-rustc_pattern_analysis"
|
||||
version = "0.80.0"
|
||||
version = "0.85.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7d0575b54ffe09bc5d2f158454bc05f0c30c01d9992310965f854be50ae22b8"
|
||||
checksum = "2f49b86e1276c1c3c72898410def29b699415f4e7d1dfb3531daf79794694372"
|
||||
dependencies = [
|
||||
"ra-ap-rustc_index",
|
||||
"rustc-hash 2.0.0",
|
||||
|
@ -1645,6 +1654,7 @@ version = "0.0.0"
|
|||
dependencies = [
|
||||
"always-assert",
|
||||
"anyhow",
|
||||
"base64",
|
||||
"cargo_metadata",
|
||||
"cfg",
|
||||
"crossbeam-channel",
|
||||
|
@ -1655,6 +1665,7 @@ dependencies = [
|
|||
"hir-def",
|
||||
"hir-ty",
|
||||
"ide",
|
||||
"ide-completion",
|
||||
"ide-db",
|
||||
"ide-ssr",
|
||||
"intern",
|
||||
|
@ -1683,6 +1694,7 @@ dependencies = [
|
|||
"stdx",
|
||||
"syntax",
|
||||
"syntax-bridge",
|
||||
"tenthash",
|
||||
"test-fixture",
|
||||
"test-utils",
|
||||
"tikv-jemallocator",
|
||||
|
@ -1986,6 +1998,12 @@ dependencies = [
|
|||
"tt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tenthash"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d67f9f3cf70e0852941d7bc3cb884b49b24b8ee956baf91ad0abae31f5ef11fb"
|
||||
|
||||
[[package]]
|
||||
name = "test-fixture"
|
||||
version = "0.0.0"
|
||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -84,11 +84,11 @@ tt = { path = "./crates/tt", version = "0.0.0" }
|
|||
vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" }
|
||||
vfs = { path = "./crates/vfs", version = "0.0.0" }
|
||||
|
||||
ra-ap-rustc_lexer = { version = "0.80", default-features = false }
|
||||
ra-ap-rustc_parse_format = { version = "0.80", default-features = false }
|
||||
ra-ap-rustc_index = { version = "0.80", default-features = false }
|
||||
ra-ap-rustc_abi = { version = "0.80", default-features = false }
|
||||
ra-ap-rustc_pattern_analysis = { version = "0.80", default-features = false }
|
||||
ra-ap-rustc_lexer = { version = "0.85", default-features = false }
|
||||
ra-ap-rustc_parse_format = { version = "0.85", default-features = false }
|
||||
ra-ap-rustc_index = { version = "0.85", default-features = false }
|
||||
ra-ap-rustc_abi = { version = "0.85", default-features = false }
|
||||
ra-ap-rustc_pattern_analysis = { version = "0.85", default-features = false }
|
||||
|
||||
# local crates that aren't published to crates.io. These should not have versions.
|
||||
test-fixture = { path = "./crates/test-fixture" }
|
||||
|
|
|
@ -547,29 +547,6 @@ impl CrateGraph {
|
|||
None
|
||||
}
|
||||
|
||||
// Work around for https://github.com/rust-lang/rust-analyzer/issues/6038.
|
||||
// As hacky as it gets.
|
||||
pub fn patch_cfg_if(&mut self) -> bool {
|
||||
// we stupidly max by version in an attempt to have all duplicated std's depend on the same cfg_if so that deduplication still works
|
||||
let cfg_if =
|
||||
self.hacky_find_crate("cfg_if").max_by_key(|&it| self.arena[it].version.clone());
|
||||
let std = self.hacky_find_crate("std").next();
|
||||
match (cfg_if, std) {
|
||||
(Some(cfg_if), Some(std)) => {
|
||||
self.arena[cfg_if].dependencies.clear();
|
||||
self.arena[std]
|
||||
.dependencies
|
||||
.push(Dependency::new(CrateName::new("cfg_if").unwrap(), cfg_if));
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn hacky_find_crate<'a>(&'a self, display_name: &'a str) -> impl Iterator<Item = CrateId> + 'a {
|
||||
self.iter().filter(move |it| self[*it].display_name.as_deref() == Some(display_name))
|
||||
}
|
||||
|
||||
/// Removes all crates from this crate graph except for the ones in `to_keep` and fixes up the dependencies.
|
||||
/// Returns a mapping from old crate ids to new crate ids.
|
||||
pub fn remove_crates_except(&mut self, to_keep: &[CrateId]) -> Vec<Option<CrateId>> {
|
||||
|
|
|
@ -31,7 +31,7 @@ use crate::{
|
|||
path::{ModPath, Path},
|
||||
src::HasSource,
|
||||
type_ref::{TypeRef, TypeRefId, TypesMap, TypesSourceMap},
|
||||
BlockId, DefWithBodyId, HasModule, Lookup,
|
||||
BlockId, DefWithBodyId, HasModule, Lookup, SyntheticSyntax,
|
||||
};
|
||||
|
||||
/// A wrapper around [`span::SyntaxContextId`] that is intended only for comparisons.
|
||||
|
@ -141,7 +141,7 @@ pub struct BodySourceMap {
|
|||
field_map_back: FxHashMap<ExprId, FieldSource>,
|
||||
pat_field_map_back: FxHashMap<PatId, PatFieldSource>,
|
||||
|
||||
types: TypesSourceMap,
|
||||
pub types: TypesSourceMap,
|
||||
|
||||
// FIXME: Make this a sane struct.
|
||||
template_map: Option<
|
||||
|
@ -160,9 +160,6 @@ pub struct BodySourceMap {
|
|||
diagnostics: Vec<BodyDiagnostic>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub struct SyntheticSyntax;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum BodyDiagnostic {
|
||||
InactiveCode { node: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions },
|
||||
|
@ -408,7 +405,8 @@ impl Body {
|
|||
f(else_branch);
|
||||
}
|
||||
}
|
||||
Expr::Let { expr, .. } => {
|
||||
Expr::Let { expr, pat } => {
|
||||
self.walk_exprs_in_pat(*pat, &mut f);
|
||||
f(*expr);
|
||||
}
|
||||
Expr::Block { statements, tail, .. }
|
||||
|
@ -442,6 +440,137 @@ impl Body {
|
|||
f(*receiver);
|
||||
args.iter().copied().for_each(f);
|
||||
}
|
||||
Expr::Match { expr, arms } => {
|
||||
f(*expr);
|
||||
arms.iter().for_each(|arm| {
|
||||
f(arm.expr);
|
||||
self.walk_exprs_in_pat(arm.pat, &mut f);
|
||||
});
|
||||
}
|
||||
Expr::Break { expr, .. }
|
||||
| Expr::Return { expr }
|
||||
| Expr::Yield { expr }
|
||||
| Expr::Yeet { expr } => {
|
||||
if let &Some(expr) = expr {
|
||||
f(expr);
|
||||
}
|
||||
}
|
||||
Expr::Become { expr } => f(*expr),
|
||||
Expr::RecordLit { fields, spread, .. } => {
|
||||
for field in fields.iter() {
|
||||
f(field.expr);
|
||||
}
|
||||
if let &Some(expr) = spread {
|
||||
f(expr);
|
||||
}
|
||||
}
|
||||
Expr::Closure { body, .. } => {
|
||||
f(*body);
|
||||
}
|
||||
Expr::BinaryOp { lhs, rhs, .. } => {
|
||||
f(*lhs);
|
||||
f(*rhs);
|
||||
}
|
||||
Expr::Range { lhs, rhs, .. } => {
|
||||
if let &Some(lhs) = rhs {
|
||||
f(lhs);
|
||||
}
|
||||
if let &Some(rhs) = lhs {
|
||||
f(rhs);
|
||||
}
|
||||
}
|
||||
Expr::Index { base, index, .. } => {
|
||||
f(*base);
|
||||
f(*index);
|
||||
}
|
||||
Expr::Field { expr, .. }
|
||||
| Expr::Await { expr }
|
||||
| Expr::Cast { expr, .. }
|
||||
| Expr::Ref { expr, .. }
|
||||
| Expr::UnaryOp { expr, .. }
|
||||
| Expr::Box { expr } => {
|
||||
f(*expr);
|
||||
}
|
||||
Expr::Tuple { exprs, .. } => exprs.iter().copied().for_each(f),
|
||||
Expr::Array(a) => match a {
|
||||
Array::ElementList { elements, .. } => elements.iter().copied().for_each(f),
|
||||
Array::Repeat { initializer, repeat } => {
|
||||
f(*initializer);
|
||||
f(*repeat)
|
||||
}
|
||||
},
|
||||
&Expr::Assignment { target, value } => {
|
||||
self.walk_exprs_in_pat(target, &mut f);
|
||||
f(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_child_exprs_without_pats(&self, expr_id: ExprId, mut f: impl FnMut(ExprId)) {
|
||||
let expr = &self[expr_id];
|
||||
match expr {
|
||||
Expr::Continue { .. }
|
||||
| Expr::Const(_)
|
||||
| Expr::Missing
|
||||
| Expr::Path(_)
|
||||
| Expr::OffsetOf(_)
|
||||
| Expr::Literal(_)
|
||||
| Expr::Underscore => {}
|
||||
Expr::InlineAsm(it) => it.operands.iter().for_each(|(_, op)| match op {
|
||||
AsmOperand::In { expr, .. }
|
||||
| AsmOperand::Out { expr: Some(expr), .. }
|
||||
| AsmOperand::InOut { expr, .. } => f(*expr),
|
||||
AsmOperand::SplitInOut { in_expr, out_expr, .. } => {
|
||||
f(*in_expr);
|
||||
if let Some(out_expr) = out_expr {
|
||||
f(*out_expr);
|
||||
}
|
||||
}
|
||||
AsmOperand::Out { expr: None, .. }
|
||||
| AsmOperand::Const(_)
|
||||
| AsmOperand::Label(_)
|
||||
| AsmOperand::Sym(_) => (),
|
||||
}),
|
||||
Expr::If { condition, then_branch, else_branch } => {
|
||||
f(*condition);
|
||||
f(*then_branch);
|
||||
if let &Some(else_branch) = else_branch {
|
||||
f(else_branch);
|
||||
}
|
||||
}
|
||||
Expr::Let { expr, .. } => {
|
||||
f(*expr);
|
||||
}
|
||||
Expr::Block { statements, tail, .. }
|
||||
| Expr::Unsafe { statements, tail, .. }
|
||||
| Expr::Async { statements, tail, .. } => {
|
||||
for stmt in statements.iter() {
|
||||
match stmt {
|
||||
Statement::Let { initializer, else_branch, .. } => {
|
||||
if let &Some(expr) = initializer {
|
||||
f(expr);
|
||||
}
|
||||
if let &Some(expr) = else_branch {
|
||||
f(expr);
|
||||
}
|
||||
}
|
||||
Statement::Expr { expr: expression, .. } => f(*expression),
|
||||
Statement::Item(_) => (),
|
||||
}
|
||||
}
|
||||
if let &Some(expr) = tail {
|
||||
f(expr);
|
||||
}
|
||||
}
|
||||
Expr::Loop { body, .. } => f(*body),
|
||||
Expr::Call { callee, args, .. } => {
|
||||
f(*callee);
|
||||
args.iter().copied().for_each(f);
|
||||
}
|
||||
Expr::MethodCall { receiver, args, .. } => {
|
||||
f(*receiver);
|
||||
args.iter().copied().for_each(f);
|
||||
}
|
||||
Expr::Match { expr, arms } => {
|
||||
f(*expr);
|
||||
arms.iter().map(|arm| arm.expr).for_each(f);
|
||||
|
@ -498,10 +627,7 @@ impl Body {
|
|||
f(*repeat)
|
||||
}
|
||||
},
|
||||
&Expr::Assignment { target, value } => {
|
||||
self.walk_exprs_in_pat(target, &mut f);
|
||||
f(value);
|
||||
}
|
||||
&Expr::Assignment { target: _, value } => f(value),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1510,20 +1510,20 @@ impl ExprCollector<'_> {
|
|||
BuiltinShadowMode::Other,
|
||||
None,
|
||||
);
|
||||
// Funnily enough, record structs/variants *can* be shadowed
|
||||
// by pattern bindings (but unit or tuple structs/variants
|
||||
// can't).
|
||||
match resolved.take_values() {
|
||||
Some(ModuleDefId::ConstId(_)) => (None, Pat::Path(name.into())),
|
||||
Some(ModuleDefId::EnumVariantId(_)) => {
|
||||
// this is only really valid for unit variants, but
|
||||
// shadowing other enum variants with a pattern is
|
||||
// an error anyway
|
||||
Some(ModuleDefId::EnumVariantId(variant))
|
||||
if self.db.variant_data(variant.into()).kind()
|
||||
!= StructKind::Record =>
|
||||
{
|
||||
(None, Pat::Path(name.into()))
|
||||
}
|
||||
Some(ModuleDefId::AdtId(AdtId::StructId(s)))
|
||||
if self.db.struct_data(s).variant_data.kind() != StructKind::Record =>
|
||||
{
|
||||
// Funnily enough, record structs *can* be shadowed
|
||||
// by pattern bindings (but unit or tuple structs
|
||||
// can't).
|
||||
(None, Pat::Path(name.into()))
|
||||
}
|
||||
// shadowing statics is an error as well, so we just ignore that case here
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
mod block;
|
||||
|
||||
use base_db::SourceDatabase;
|
||||
use expect_test::{expect, Expect};
|
||||
use test_fixture::WithFixture;
|
||||
|
||||
|
@ -11,7 +10,7 @@ use super::*;
|
|||
fn lower(ra_fixture: &str) -> (TestDB, Arc<Body>, DefWithBodyId) {
|
||||
let db = TestDB::with_files(ra_fixture);
|
||||
|
||||
let krate = db.crate_graph().iter().next().unwrap();
|
||||
let krate = db.fetch_test_crate();
|
||||
let def_map = db.crate_def_map(krate);
|
||||
let mut fn_def = None;
|
||||
'outer: for (_, module) in def_map.modules() {
|
||||
|
@ -404,3 +403,26 @@ fn foo() {
|
|||
}"#]]
|
||||
.assert_eq(&body.pretty_print(&db, def, Edition::CURRENT))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shadowing_record_variant() {
|
||||
let (_, body, _) = lower(
|
||||
r#"
|
||||
enum A {
|
||||
B { field: i32 },
|
||||
}
|
||||
fn f() {
|
||||
use A::*;
|
||||
match () {
|
||||
B => {}
|
||||
};
|
||||
}
|
||||
"#,
|
||||
);
|
||||
assert_eq!(body.bindings.len(), 1, "should have a binding for `B`");
|
||||
assert_eq!(
|
||||
body.bindings[BindingId::from_raw(RawIdx::from_u32(0))].name.as_str(),
|
||||
"B",
|
||||
"should have a binding for `B`",
|
||||
);
|
||||
}
|
||||
|
|
|
@ -369,7 +369,7 @@ impl ImplData {
|
|||
|
||||
let item_tree = tree_id.item_tree(db);
|
||||
let impl_def = &item_tree[tree_id.value];
|
||||
let target_trait = impl_def.target_trait.clone();
|
||||
let target_trait = impl_def.target_trait;
|
||||
let self_ty = impl_def.self_ty;
|
||||
let is_negative = impl_def.is_negative;
|
||||
let is_unsafe = impl_def.is_unsafe;
|
||||
|
|
|
@ -26,8 +26,8 @@ use crate::{
|
|||
nameres::{DefMap, MacroSubNs},
|
||||
path::{AssociatedTypeBinding, GenericArg, GenericArgs, NormalPath, Path},
|
||||
type_ref::{
|
||||
ArrayType, ConstRef, FnType, LifetimeRef, RefType, TypeBound, TypeRef, TypeRefId, TypesMap,
|
||||
TypesSourceMap,
|
||||
ArrayType, ConstRef, FnType, LifetimeRef, PathId, RefType, TypeBound, TypeRef, TypeRefId,
|
||||
TypesMap, TypesSourceMap,
|
||||
},
|
||||
AdtId, ConstParamId, GenericDefId, HasModule, ItemTreeLoc, LifetimeParamId,
|
||||
LocalLifetimeParamId, LocalTypeOrConstParamId, Lookup, TypeOrConstParamId, TypeParamId,
|
||||
|
@ -224,6 +224,11 @@ impl GenericParams {
|
|||
self.len() == 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn no_predicates(&self) -> bool {
|
||||
self.where_predicates.is_empty()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn where_predicates(&self) -> std::slice::Iter<'_, WherePredicate> {
|
||||
self.where_predicates.iter()
|
||||
|
@ -874,14 +879,20 @@ fn copy_type_bound(
|
|||
to: &mut TypesMap,
|
||||
to_source_map: &mut TypesSourceMap,
|
||||
) -> TypeBound {
|
||||
match bound {
|
||||
TypeBound::Path(path, modifier) => {
|
||||
TypeBound::Path(copy_path(path, from, from_source_map, to, to_source_map), *modifier)
|
||||
let mut copy_path_id = |path: PathId| {
|
||||
let new_path = copy_path(&from[path], from, from_source_map, to, to_source_map);
|
||||
let new_path_id = to.types.alloc(TypeRef::Path(new_path));
|
||||
if let Some(&ptr) = from_source_map.types_map_back.get(path.type_ref()) {
|
||||
to_source_map.types_map_back.insert(new_path_id, ptr);
|
||||
}
|
||||
PathId::from_type_ref_unchecked(new_path_id)
|
||||
};
|
||||
|
||||
match bound {
|
||||
&TypeBound::Path(path, modifier) => TypeBound::Path(copy_path_id(path), modifier),
|
||||
TypeBound::ForLifetime(lifetimes, path) => {
|
||||
TypeBound::ForLifetime(lifetimes.clone(), copy_path_id(*path))
|
||||
}
|
||||
TypeBound::ForLifetime(lifetimes, path) => TypeBound::ForLifetime(
|
||||
lifetimes.clone(),
|
||||
copy_path(path, from, from_source_map, to, to_source_map),
|
||||
),
|
||||
TypeBound::Lifetime(lifetime) => TypeBound::Lifetime(lifetime.clone()),
|
||||
TypeBound::Use(use_args) => TypeBound::Use(use_args.clone()),
|
||||
TypeBound::Error => TypeBound::Error,
|
||||
|
|
|
@ -23,6 +23,7 @@ use crate::{
|
|||
hir::Literal,
|
||||
lower::LowerCtx,
|
||||
path::{GenericArg, Path},
|
||||
SyntheticSyntax,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
|
@ -91,19 +92,37 @@ impl Rawness {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
/// A `TypeRefId` that is guaranteed to always be `TypeRef::Path`. We use this for things like
|
||||
/// impl's trait, that are always paths but need to be traced back to source code.
|
||||
pub struct PathId(TypeRefId);
|
||||
|
||||
impl PathId {
|
||||
#[inline]
|
||||
pub fn from_type_ref_unchecked(type_ref: TypeRefId) -> Self {
|
||||
Self(type_ref)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn type_ref(self) -> TypeRefId {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct TraitRef {
|
||||
pub path: Path,
|
||||
pub path: PathId,
|
||||
}
|
||||
|
||||
impl TraitRef {
|
||||
/// Converts an `ast::PathType` to a `hir::TraitRef`.
|
||||
pub(crate) fn from_ast(ctx: &mut LowerCtx<'_>, node: ast::Type) -> Option<Self> {
|
||||
// FIXME: Use `Path::from_src`
|
||||
match node {
|
||||
ast::Type::PathType(path) => {
|
||||
path.path().and_then(|it| ctx.lower_path(it)).map(|path| TraitRef { path })
|
||||
}
|
||||
match &node {
|
||||
ast::Type::PathType(path) => path
|
||||
.path()
|
||||
.and_then(|it| ctx.lower_path(it))
|
||||
.map(|path| TraitRef { path: ctx.alloc_path(path, AstPtr::new(&node)) }),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -173,11 +192,24 @@ impl TypesMap {
|
|||
impl Index<TypeRefId> for TypesMap {
|
||||
type Output = TypeRef;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, index: TypeRefId) -> &Self::Output {
|
||||
&self.types[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<PathId> for TypesMap {
|
||||
type Output = Path;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, index: PathId) -> &Self::Output {
|
||||
let TypeRef::Path(path) = &self[index.type_ref()] else {
|
||||
unreachable!("`PathId` always points to `TypeRef::Path`");
|
||||
};
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
pub type TypePtr = AstPtr<ast::Type>;
|
||||
pub type TypeSource = InFile<TypePtr>;
|
||||
|
||||
|
@ -187,6 +219,12 @@ pub struct TypesSourceMap {
|
|||
}
|
||||
|
||||
impl TypesSourceMap {
|
||||
pub const EMPTY: Self = Self { types_map_back: ArenaMap::new() };
|
||||
|
||||
pub fn type_syntax(&self, id: TypeRefId) -> Result<TypeSource, SyntheticSyntax> {
|
||||
self.types_map_back.get(id).cloned().ok_or(SyntheticSyntax)
|
||||
}
|
||||
|
||||
pub(crate) fn shrink_to_fit(&mut self) {
|
||||
let TypesSourceMap { types_map_back } = self;
|
||||
types_map_back.shrink_to_fit();
|
||||
|
@ -214,15 +252,15 @@ impl LifetimeRef {
|
|||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum TypeBound {
|
||||
Path(Path, TraitBoundModifier),
|
||||
ForLifetime(Box<[Name]>, Path),
|
||||
Path(PathId, TraitBoundModifier),
|
||||
ForLifetime(Box<[Name]>, PathId),
|
||||
Lifetime(LifetimeRef),
|
||||
Use(Box<[UseArgRef]>),
|
||||
Error,
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
const _: [(); 32] = [(); ::std::mem::size_of::<TypeBound>()];
|
||||
const _: [(); 24] = [(); ::std::mem::size_of::<TypeBound>()];
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum UseArgRef {
|
||||
|
@ -365,8 +403,8 @@ impl TypeRef {
|
|||
TypeRef::ImplTrait(bounds) | TypeRef::DynTrait(bounds) => {
|
||||
for bound in bounds {
|
||||
match bound {
|
||||
TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => {
|
||||
go_path(path, f, map)
|
||||
&TypeBound::Path(path, _) | &TypeBound::ForLifetime(_, path) => {
|
||||
go_path(&map[path], f, map)
|
||||
}
|
||||
TypeBound::Lifetime(_) | TypeBound::Error | TypeBound::Use(_) => (),
|
||||
}
|
||||
|
@ -397,8 +435,8 @@ impl TypeRef {
|
|||
}
|
||||
for bound in binding.bounds.iter() {
|
||||
match bound {
|
||||
TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => {
|
||||
go_path(path, f, map)
|
||||
&TypeBound::Path(path, _) | &TypeBound::ForLifetime(_, path) => {
|
||||
go_path(&map[path], f, map)
|
||||
}
|
||||
TypeBound::Lifetime(_) | TypeBound::Error | TypeBound::Use(_) => (),
|
||||
}
|
||||
|
@ -425,7 +463,7 @@ pub(crate) fn type_bounds_from_ast(
|
|||
|
||||
impl TypeBound {
|
||||
pub(crate) fn from_ast(ctx: &mut LowerCtx<'_>, node: ast::TypeBound) -> Self {
|
||||
let mut lower_path_type = |path_type: ast::PathType| ctx.lower_path(path_type.path()?);
|
||||
let mut lower_path_type = |path_type: &ast::PathType| ctx.lower_path(path_type.path()?);
|
||||
|
||||
match node.kind() {
|
||||
ast::TypeBoundKind::PathType(path_type) => {
|
||||
|
@ -433,8 +471,10 @@ impl TypeBound {
|
|||
Some(_) => TraitBoundModifier::Maybe,
|
||||
None => TraitBoundModifier::None,
|
||||
};
|
||||
lower_path_type(path_type)
|
||||
.map(|p| TypeBound::Path(p, m))
|
||||
lower_path_type(&path_type)
|
||||
.map(|p| {
|
||||
TypeBound::Path(ctx.alloc_path(p, AstPtr::new(&path_type).upcast()), m)
|
||||
})
|
||||
.unwrap_or(TypeBound::Error)
|
||||
}
|
||||
ast::TypeBoundKind::ForType(for_type) => {
|
||||
|
@ -445,12 +485,14 @@ impl TypeBound {
|
|||
.collect(),
|
||||
None => Box::default(),
|
||||
};
|
||||
let path = for_type.ty().and_then(|ty| match ty {
|
||||
ast::Type::PathType(path_type) => lower_path_type(path_type),
|
||||
let path = for_type.ty().and_then(|ty| match &ty {
|
||||
ast::Type::PathType(path_type) => lower_path_type(path_type).map(|p| (p, ty)),
|
||||
_ => None,
|
||||
});
|
||||
match path {
|
||||
Some(p) => TypeBound::ForLifetime(lt_refs, p),
|
||||
Some((p, ty)) => {
|
||||
TypeBound::ForLifetime(lt_refs, ctx.alloc_path(p, AstPtr::new(&ty)))
|
||||
}
|
||||
None => TypeBound::Error,
|
||||
}
|
||||
}
|
||||
|
@ -470,10 +512,10 @@ impl TypeBound {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn as_path(&self) -> Option<(&Path, &TraitBoundModifier)> {
|
||||
pub fn as_path<'a>(&self, map: &'a TypesMap) -> Option<(&'a Path, TraitBoundModifier)> {
|
||||
match self {
|
||||
TypeBound::Path(p, m) => Some((p, m)),
|
||||
TypeBound::ForLifetime(_, p) => Some((p, &TraitBoundModifier::None)),
|
||||
&TypeBound::Path(p, m) => Some((&map[p], m)),
|
||||
&TypeBound::ForLifetime(_, p) => Some((&map[p], TraitBoundModifier::None)),
|
||||
TypeBound::Lifetime(_) | TypeBound::Error | TypeBound::Use(_) => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ use crate::{
|
|||
lower::LowerCtx,
|
||||
path::AssociatedTypeBinding,
|
||||
type_ref::{
|
||||
LifetimeRef, RefType, TraitBoundModifier, TraitRef, TypeBound, TypeRef, TypeRefId,
|
||||
LifetimeRef, PathId, RefType, TraitBoundModifier, TraitRef, TypeBound, TypeRef, TypeRefId,
|
||||
TypesMap, TypesSourceMap,
|
||||
},
|
||||
visibility::RawVisibility,
|
||||
|
@ -514,7 +514,7 @@ impl<'a> Ctx<'a> {
|
|||
};
|
||||
|
||||
let ret_type = if func.async_token().is_some() {
|
||||
let future_impl = desugar_future_path(ret_type);
|
||||
let future_impl = desugar_future_path(&mut body_ctx, ret_type);
|
||||
let ty_bound = TypeBound::Path(future_impl, TraitBoundModifier::None);
|
||||
body_ctx.alloc_type_ref_desugared(TypeRef::ImplTrait(ThinVec::from_iter([ty_bound])))
|
||||
} else {
|
||||
|
@ -936,7 +936,7 @@ impl<'a> Ctx<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn desugar_future_path(orig: TypeRefId) -> Path {
|
||||
fn desugar_future_path(ctx: &mut LowerCtx<'_>, orig: TypeRefId) -> PathId {
|
||||
let path = path![core::future::Future];
|
||||
let mut generic_args: Vec<_> =
|
||||
std::iter::repeat(None).take(path.segments().len() - 1).collect();
|
||||
|
@ -948,7 +948,8 @@ fn desugar_future_path(orig: TypeRefId) -> Path {
|
|||
};
|
||||
generic_args.push(Some(GenericArgs { bindings: Box::new([binding]), ..GenericArgs::empty() }));
|
||||
|
||||
Path::from_known_path(path, generic_args)
|
||||
let path = Path::from_known_path(path, generic_args);
|
||||
PathId::from_type_ref_unchecked(ctx.alloc_type_ref_desugared(TypeRef::Path(path)))
|
||||
}
|
||||
|
||||
enum HasImplicitSelf {
|
||||
|
|
|
@ -484,7 +484,7 @@ impl Printer<'_> {
|
|||
w!(self, "!");
|
||||
}
|
||||
if let Some(tr) = target_trait {
|
||||
self.print_path(&tr.path, types_map);
|
||||
self.print_path(&types_map[tr.path], types_map);
|
||||
w!(self, " for ");
|
||||
}
|
||||
self.print_type_ref(*self_ty, types_map);
|
||||
|
@ -648,9 +648,9 @@ impl Printer<'_> {
|
|||
let (target, bound) = match pred {
|
||||
WherePredicate::TypeBound { target, bound } => (target, bound),
|
||||
WherePredicate::Lifetime { target, bound } => {
|
||||
wln!(
|
||||
w!(
|
||||
this,
|
||||
"{}: {},",
|
||||
"{}: {}",
|
||||
target.name.display(self.db.upcast(), edition),
|
||||
bound.name.display(self.db.upcast(), edition)
|
||||
);
|
||||
|
|
|
@ -351,7 +351,8 @@ trait Tr<'a, T: 'a>: Super where Self: for<'a> Tr<'a, T> {}
|
|||
where
|
||||
T: Copy,
|
||||
T: 'a,
|
||||
T: 'b
|
||||
T: 'b,
|
||||
'b: 'a
|
||||
{
|
||||
pub(self) field: &'a &'b T,
|
||||
}
|
||||
|
@ -370,7 +371,8 @@ trait Tr<'a, T: 'a>: Super where Self: for<'a> Tr<'a, T> {}
|
|||
where
|
||||
T: Copy,
|
||||
T: 'a,
|
||||
T: 'b
|
||||
T: 'b,
|
||||
'b: 'a
|
||||
{
|
||||
// AstId: 9
|
||||
pub(self) fn f<G>(
|
||||
|
|
|
@ -376,6 +376,9 @@ language_item_table! {
|
|||
Fn, sym::fn_, fn_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
FnMut, sym::fn_mut, fn_mut_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
FnOnce, sym::fn_once, fn_once_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
AsyncFn, sym::async_fn, async_fn_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
AsyncFnMut, sym::async_fn_mut, async_fn_mut_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
AsyncFnOnce, sym::async_fn_once, async_fn_once_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||
|
||||
FnOnceOutput, sym::fn_once_output, fn_once_output, Target::AssocTy, GenericRequirement::None;
|
||||
|
||||
|
|
|
@ -1535,3 +1535,6 @@ fn macro_call_as_call_id_with_eager(
|
|||
pub struct UnresolvedMacro {
|
||||
pub path: hir_expand::mod_path::ModPath,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub struct SyntheticSyntax;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
use std::{cell::OnceCell, mem};
|
||||
|
||||
use hir_expand::{span_map::SpanMap, AstId, HirFileId, InFile};
|
||||
use span::{AstIdMap, AstIdNode};
|
||||
use span::{AstIdMap, AstIdNode, Edition, EditionedFileId, FileId, RealSpanMap};
|
||||
use stdx::thin_vec::ThinVec;
|
||||
use syntax::ast;
|
||||
use triomphe::Arc;
|
||||
|
@ -10,7 +10,7 @@ use triomphe::Arc;
|
|||
use crate::{
|
||||
db::DefDatabase,
|
||||
path::Path,
|
||||
type_ref::{TypeBound, TypePtr, TypeRef, TypeRefId, TypesMap, TypesSourceMap},
|
||||
type_ref::{PathId, TypeBound, TypePtr, TypeRef, TypeRefId, TypesMap, TypesSourceMap},
|
||||
};
|
||||
|
||||
pub struct LowerCtx<'a> {
|
||||
|
@ -63,6 +63,30 @@ impl<'a> LowerCtx<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Prepares a `LowerCtx` for synthetic AST that needs to be lowered. This is intended for IDE things.
|
||||
pub fn for_synthetic_ast(
|
||||
db: &'a dyn DefDatabase,
|
||||
ast_id_map: Arc<AstIdMap>,
|
||||
types_map: &'a mut TypesMap,
|
||||
types_source_map: &'a mut TypesSourceMap,
|
||||
) -> Self {
|
||||
let file_id = EditionedFileId::new(
|
||||
FileId::from_raw(EditionedFileId::MAX_FILE_ID),
|
||||
Edition::Edition2015,
|
||||
);
|
||||
LowerCtx {
|
||||
db,
|
||||
// Make up an invalid file id, so that if we will try to actually access it salsa will panic.
|
||||
file_id: file_id.into(),
|
||||
span_map: SpanMap::RealSpanMap(Arc::new(RealSpanMap::absolute(file_id))).into(),
|
||||
ast_id_map: ast_id_map.into(),
|
||||
impl_trait_bounds: Vec::new(),
|
||||
outer_impl_trait: false,
|
||||
types_map,
|
||||
types_source_map,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn span_map(&self) -> &SpanMap {
|
||||
self.span_map.get_or_init(|| self.db.span_map(self.file_id))
|
||||
}
|
||||
|
@ -118,4 +142,8 @@ impl<'a> LowerCtx<'a> {
|
|||
pub(crate) fn alloc_error_type(&mut self) -> TypeRefId {
|
||||
self.types_map.types.alloc(TypeRef::Error)
|
||||
}
|
||||
|
||||
pub(crate) fn alloc_path(&mut self, path: Path, node: TypePtr) -> PathId {
|
||||
PathId::from_type_ref_unchecked(self.alloc_type_ref(TypeRef::Path(path), node))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1733,7 +1733,7 @@ m!(C("0"));
|
|||
macro_rules! m {
|
||||
($k:expr) => { fn f() { K::$k; } }
|
||||
}
|
||||
/* parse error: expected identifier */
|
||||
/* parse error: expected identifier, `self`, `super`, `crate`, or `Self` */
|
||||
/* parse error: expected SEMICOLON */
|
||||
/* parse error: expected SEMICOLON */
|
||||
/* parse error: expected expression, item or let statement */
|
||||
|
@ -1759,8 +1759,9 @@ fn f() {
|
|||
// NAME_REF@6..7
|
||||
// IDENT@6..7 "K"
|
||||
// COLON2@7..9 "::"
|
||||
// ERROR@9..10
|
||||
// L_PAREN@9..10 "("
|
||||
// PATH_SEGMENT@9..10
|
||||
// ERROR@9..10
|
||||
// L_PAREN@9..10 "("
|
||||
// EXPR_STMT@10..16
|
||||
// CALL_EXPR@10..16
|
||||
// PATH_EXPR@10..11
|
||||
|
|
|
@ -184,3 +184,31 @@ fn test() {
|
|||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn meta_variable_raw_name_equals_non_raw() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m {
|
||||
($r#name:tt) => {
|
||||
$name
|
||||
}
|
||||
}
|
||||
|
||||
fn test() {
|
||||
m!(1234)
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! m {
|
||||
($r#name:tt) => {
|
||||
$name
|
||||
}
|
||||
}
|
||||
|
||||
fn test() {
|
||||
1234
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ mod proc_macros;
|
|||
|
||||
use std::{iter, ops::Range, sync};
|
||||
|
||||
use base_db::SourceDatabase;
|
||||
use expect_test::Expect;
|
||||
use hir_expand::{
|
||||
db::ExpandDatabase,
|
||||
|
@ -63,7 +62,7 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream
|
|||
},
|
||||
)];
|
||||
let db = TestDB::with_files_extra_proc_macros(ra_fixture, extra_proc_macros);
|
||||
let krate = db.crate_graph().iter().next().unwrap();
|
||||
let krate = db.fetch_test_crate();
|
||||
let def_map = db.crate_def_map(krate);
|
||||
let local_id = DefMap::ROOT;
|
||||
let module = def_map.module_id(local_id);
|
||||
|
|
|
@ -910,8 +910,13 @@ impl DefCollector<'_> {
|
|||
self.update(module_id, &items, vis, Some(ImportType::Glob(id)));
|
||||
// record the glob import in case we add further items
|
||||
let glob = self.glob_imports.entry(m.local_id).or_default();
|
||||
if !glob.iter().any(|(mid, _, _)| *mid == module_id) {
|
||||
glob.push((module_id, vis, id));
|
||||
match glob.iter_mut().find(|(mid, _, _)| *mid == module_id) {
|
||||
None => glob.push((module_id, vis, id)),
|
||||
Some((_, old_vis, _)) => {
|
||||
if let Some(new_vis) = old_vis.max(vis, &self.def_map) {
|
||||
*old_vis = new_vis;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,13 +13,13 @@ use crate::{db::DefDatabase, nameres::DefMap, test_db::TestDB};
|
|||
|
||||
fn compute_crate_def_map(ra_fixture: &str) -> Arc<DefMap> {
|
||||
let db = TestDB::with_files(ra_fixture);
|
||||
let krate = db.crate_graph().iter().next().unwrap();
|
||||
let krate = db.fetch_test_crate();
|
||||
db.crate_def_map(krate)
|
||||
}
|
||||
|
||||
fn render_crate_def_map(ra_fixture: &str) -> String {
|
||||
let db = TestDB::with_files(ra_fixture);
|
||||
let krate = db.crate_graph().iter().next().unwrap();
|
||||
let krate = db.fetch_test_crate();
|
||||
db.crate_def_map(krate).dump(&db)
|
||||
}
|
||||
|
||||
|
|
|
@ -451,3 +451,42 @@ mod glob_target {
|
|||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_18580() {
|
||||
check(
|
||||
r#"
|
||||
pub mod libs {
|
||||
pub struct Placeholder;
|
||||
}
|
||||
|
||||
pub mod reexport_2 {
|
||||
use reexport_1::*;
|
||||
pub use reexport_1::*;
|
||||
|
||||
pub mod reexport_1 {
|
||||
pub use crate::libs::*;
|
||||
}
|
||||
}
|
||||
|
||||
use reexport_2::*;
|
||||
"#,
|
||||
expect![[r#"
|
||||
crate
|
||||
Placeholder: t v
|
||||
libs: t
|
||||
reexport_1: t
|
||||
reexport_2: t
|
||||
|
||||
crate::libs
|
||||
Placeholder: t v
|
||||
|
||||
crate::reexport_2
|
||||
Placeholder: t v
|
||||
reexport_1: t
|
||||
|
||||
crate::reexport_2::reexport_1
|
||||
Placeholder: t v
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
use base_db::{SourceDatabase, SourceDatabaseFileInputExt as _};
|
||||
use base_db::SourceDatabaseFileInputExt as _;
|
||||
use test_fixture::WithFixture;
|
||||
|
||||
use crate::{db::DefDatabase, nameres::tests::TestDB, AdtId, ModuleDefId};
|
||||
|
||||
fn check_def_map_is_not_recomputed(ra_fixture_initial: &str, ra_fixture_change: &str) {
|
||||
let (mut db, pos) = TestDB::with_position(ra_fixture_initial);
|
||||
let krate = {
|
||||
let crate_graph = db.crate_graph();
|
||||
// Some of these tests use minicore/proc-macros which will be injected as the first crate
|
||||
crate_graph.iter().last().unwrap()
|
||||
};
|
||||
let krate = db.fetch_test_crate();
|
||||
{
|
||||
let events = db.log_executed(|| {
|
||||
db.crate_def_map(krate);
|
||||
|
@ -120,28 +116,31 @@ fn f() { foo }
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typing_inside_an_attribute_arg_should_not_invalidate_def_map() {
|
||||
check_def_map_is_not_recomputed(
|
||||
r"
|
||||
//- proc_macros: identity
|
||||
//- /lib.rs
|
||||
mod foo;
|
||||
// Would be nice if this was the case, but as attribute inputs are stored in the item tree, this is
|
||||
// not currently the case.
|
||||
// #[test]
|
||||
// fn typing_inside_an_attribute_arg_should_not_invalidate_def_map() {
|
||||
// check_def_map_is_not_recomputed(
|
||||
// r"
|
||||
// //- proc_macros: identity
|
||||
// //- /lib.rs
|
||||
// mod foo;
|
||||
|
||||
//- /foo/mod.rs
|
||||
pub mod bar;
|
||||
// //- /foo/mod.rs
|
||||
// pub mod bar;
|
||||
|
||||
// //- /foo/bar.rs
|
||||
// $0
|
||||
// #[proc_macros::identity]
|
||||
// fn f() {}
|
||||
// ",
|
||||
// r"
|
||||
// #[proc_macros::identity(foo)]
|
||||
// fn f() {}
|
||||
// ",
|
||||
// );
|
||||
// }
|
||||
|
||||
//- /foo/bar.rs
|
||||
$0
|
||||
#[proc_macros::identity]
|
||||
fn f() {}
|
||||
",
|
||||
r"
|
||||
#[proc_macros::identity(foo)]
|
||||
fn f() {}
|
||||
",
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn typing_inside_macro_heavy_file_should_not_invalidate_def_map() {
|
||||
check_def_map_is_not_recomputed(
|
||||
|
@ -198,31 +197,33 @@ pub struct S {}
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typing_inside_a_derive_should_not_invalidate_def_map() {
|
||||
check_def_map_is_not_recomputed(
|
||||
r"
|
||||
//- proc_macros: derive_identity
|
||||
//- minicore:derive
|
||||
//- /lib.rs
|
||||
mod foo;
|
||||
// Would be nice if this was the case, but as attribute inputs are stored in the item tree, this is
|
||||
// not currently the case.
|
||||
// #[test]
|
||||
// fn typing_inside_a_derive_should_not_invalidate_def_map() {
|
||||
// check_def_map_is_not_recomputed(
|
||||
// r"
|
||||
// //- proc_macros: derive_identity
|
||||
// //- minicore:derive
|
||||
// //- /lib.rs
|
||||
// mod foo;
|
||||
|
||||
//- /foo/mod.rs
|
||||
pub mod bar;
|
||||
// //- /foo/mod.rs
|
||||
// pub mod bar;
|
||||
|
||||
//- /foo/bar.rs
|
||||
$0
|
||||
#[derive(proc_macros::DeriveIdentity)]
|
||||
#[allow()]
|
||||
struct S;
|
||||
",
|
||||
r"
|
||||
#[derive(proc_macros::DeriveIdentity)]
|
||||
#[allow(dead_code)]
|
||||
struct S;
|
||||
",
|
||||
);
|
||||
}
|
||||
// //- /foo/bar.rs
|
||||
// $0
|
||||
// #[derive(proc_macros::DeriveIdentity)]
|
||||
// #[allow()]
|
||||
// struct S;
|
||||
// ",
|
||||
// r"
|
||||
// #[derive(proc_macros::DeriveIdentity)]
|
||||
// #[allow(dead_code)]
|
||||
// struct S;
|
||||
// ",
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn typing_inside_a_function_should_not_invalidate_item_expansions() {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! A desugared representation of paths like `crate::foo` or `<Type as Trait>::bar`.
|
||||
mod lower;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
|
@ -19,6 +21,8 @@ use syntax::ast;
|
|||
|
||||
pub use hir_expand::mod_path::{path, ModPath, PathKind};
|
||||
|
||||
pub use lower::hir_segment_to_ast_segment;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ImportAlias {
|
||||
/// Unnamed alias, as in `use Foo as _;`
|
||||
|
@ -230,7 +234,7 @@ impl Path {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct PathSegment<'a> {
|
||||
pub name: &'a Name,
|
||||
pub args_and_bindings: Option<&'a GenericArgs>,
|
||||
|
@ -274,6 +278,12 @@ impl<'a> PathSegments<'a> {
|
|||
generic_args: self.generic_args.map(|it| it.get(..len).unwrap_or(it)),
|
||||
}
|
||||
}
|
||||
pub fn strip_last(&self) -> PathSegments<'a> {
|
||||
PathSegments {
|
||||
segments: self.segments.split_last().map_or(&[], |it| it.1),
|
||||
generic_args: self.generic_args.map(|it| it.split_last().map_or(&[][..], |it| it.1)),
|
||||
}
|
||||
}
|
||||
pub fn iter(&self) -> impl Iterator<Item = PathSegment<'a>> {
|
||||
self.segments
|
||||
.iter()
|
||||
|
|
|
@ -17,13 +17,31 @@ use crate::{
|
|||
type_ref::{LifetimeRef, TypeBound, TypeRef},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
thread_local! {
|
||||
/// This is used to test `hir_segment_to_ast_segment()`. It's a hack, but it makes testing much easier.
|
||||
pub(super) static SEGMENT_LOWERING_MAP: std::cell::RefCell<rustc_hash::FxHashMap<ast::PathSegment, usize>> = std::cell::RefCell::default();
|
||||
}
|
||||
|
||||
/// Converts an `ast::Path` to `Path`. Works with use trees.
|
||||
/// It correctly handles `$crate` based path from macro call.
|
||||
// If you modify the logic of the lowering, make sure to check if `hir_segment_to_ast_segment()`
|
||||
// also needs an update.
|
||||
pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<Path> {
|
||||
let mut kind = PathKind::Plain;
|
||||
let mut type_anchor = None;
|
||||
let mut segments = Vec::new();
|
||||
let mut generic_args = Vec::new();
|
||||
#[cfg(test)]
|
||||
let mut ast_segments = Vec::new();
|
||||
#[cfg(test)]
|
||||
let mut ast_segments_offset = 0;
|
||||
#[allow(unused_mut)]
|
||||
let mut push_segment = |_segment: &ast::PathSegment, segments: &mut Vec<Name>, name| {
|
||||
#[cfg(test)]
|
||||
ast_segments.push(_segment.clone());
|
||||
segments.push(name);
|
||||
};
|
||||
loop {
|
||||
let segment = path.segment()?;
|
||||
|
||||
|
@ -34,6 +52,10 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
|
|||
match segment.kind()? {
|
||||
ast::PathSegmentKind::Name(name_ref) => {
|
||||
if name_ref.text() == "$crate" {
|
||||
if path.qualifier().is_some() {
|
||||
// FIXME: Report an error.
|
||||
return None;
|
||||
}
|
||||
break kind = resolve_crate_root(
|
||||
ctx.db.upcast(),
|
||||
ctx.span_map().span_for_range(name_ref.syntax().text_range()).ctx,
|
||||
|
@ -48,7 +70,7 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
|
|||
.or_else(|| {
|
||||
lower_generic_args_from_fn_path(
|
||||
ctx,
|
||||
segment.param_list(),
|
||||
segment.parenthesized_arg_list(),
|
||||
segment.ret_type(),
|
||||
)
|
||||
});
|
||||
|
@ -56,10 +78,10 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
|
|||
generic_args.resize(segments.len(), None);
|
||||
generic_args.push(args);
|
||||
}
|
||||
segments.push(name);
|
||||
push_segment(&segment, &mut segments, name);
|
||||
}
|
||||
ast::PathSegmentKind::SelfTypeKw => {
|
||||
segments.push(Name::new_symbol_root(sym::Self_.clone()));
|
||||
push_segment(&segment, &mut segments, Name::new_symbol_root(sym::Self_.clone()));
|
||||
}
|
||||
ast::PathSegmentKind::Type { type_ref, trait_ref } => {
|
||||
assert!(path.qualifier().is_none()); // this can only occur at the first segment
|
||||
|
@ -81,6 +103,10 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
|
|||
kind = mod_path.kind;
|
||||
|
||||
segments.extend(mod_path.segments().iter().cloned().rev());
|
||||
#[cfg(test)]
|
||||
{
|
||||
ast_segments_offset = mod_path.segments().len();
|
||||
}
|
||||
if let Some(path_generic_args) = path_generic_args {
|
||||
generic_args.resize(segments.len() - num_segments, None);
|
||||
generic_args.extend(Vec::from(path_generic_args).into_iter().rev());
|
||||
|
@ -112,10 +138,18 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
|
|||
}
|
||||
}
|
||||
ast::PathSegmentKind::CrateKw => {
|
||||
if path.qualifier().is_some() {
|
||||
// FIXME: Report an error.
|
||||
return None;
|
||||
}
|
||||
kind = PathKind::Crate;
|
||||
break;
|
||||
}
|
||||
ast::PathSegmentKind::SelfKw => {
|
||||
if path.qualifier().is_some() {
|
||||
// FIXME: Report an error.
|
||||
return None;
|
||||
}
|
||||
// don't break out if `self` is the last segment of a path, this mean we got a
|
||||
// use tree like `foo::{self}` which we want to resolve as `foo`
|
||||
if !segments.is_empty() {
|
||||
|
@ -162,6 +196,13 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
{
|
||||
ast_segments.reverse();
|
||||
SEGMENT_LOWERING_MAP
|
||||
.with_borrow_mut(|map| map.extend(ast_segments.into_iter().zip(ast_segments_offset..)));
|
||||
}
|
||||
|
||||
let mod_path = Interned::new(ModPath::from_segments(kind, segments));
|
||||
if type_anchor.is_none() && generic_args.is_empty() {
|
||||
return Some(Path::BarePath(mod_path));
|
||||
|
@ -181,6 +222,41 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
|
|||
}
|
||||
}
|
||||
|
||||
/// This function finds the AST segment that corresponds to the HIR segment
|
||||
/// with index `segment_idx` on the path that is lowered from `path`.
|
||||
pub fn hir_segment_to_ast_segment(path: &ast::Path, segment_idx: u32) -> Option<ast::PathSegment> {
|
||||
// Too tightly coupled to `lower_path()`, but unfortunately we cannot decouple them,
|
||||
// as keeping source maps for all paths segments will have a severe impact on memory usage.
|
||||
|
||||
let mut segments = path.segments();
|
||||
if let Some(ast::PathSegmentKind::Type { trait_ref: Some(trait_ref), .. }) =
|
||||
segments.clone().next().and_then(|it| it.kind())
|
||||
{
|
||||
segments.next();
|
||||
return find_segment(trait_ref.path()?.segments().chain(segments), segment_idx);
|
||||
}
|
||||
return find_segment(segments, segment_idx);
|
||||
|
||||
fn find_segment(
|
||||
segments: impl Iterator<Item = ast::PathSegment>,
|
||||
segment_idx: u32,
|
||||
) -> Option<ast::PathSegment> {
|
||||
segments
|
||||
.filter(|segment| match segment.kind() {
|
||||
Some(
|
||||
ast::PathSegmentKind::CrateKw
|
||||
| ast::PathSegmentKind::SelfKw
|
||||
| ast::PathSegmentKind::SuperKw
|
||||
| ast::PathSegmentKind::Type { .. },
|
||||
)
|
||||
| None => false,
|
||||
Some(ast::PathSegmentKind::Name(name)) => name.text() != "$crate",
|
||||
Some(ast::PathSegmentKind::SelfTypeKw) => true,
|
||||
})
|
||||
.nth(segment_idx as usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn lower_generic_args(
|
||||
lower_ctx: &mut LowerCtx<'_>,
|
||||
node: ast::GenericArgList,
|
||||
|
@ -247,12 +323,12 @@ pub(super) fn lower_generic_args(
|
|||
/// -> Z` (which desugars to `Fn<(X, Y), Output=Z>`).
|
||||
fn lower_generic_args_from_fn_path(
|
||||
ctx: &mut LowerCtx<'_>,
|
||||
params: Option<ast::ParamList>,
|
||||
args: Option<ast::ParenthesizedArgList>,
|
||||
ret_type: Option<ast::RetType>,
|
||||
) -> Option<GenericArgs> {
|
||||
let params = params?;
|
||||
let params = args?;
|
||||
let mut param_types = Vec::new();
|
||||
for param in params.params() {
|
||||
for param in params.type_args() {
|
||||
let type_ref = TypeRef::from_ast_opt(ctx, param.ty());
|
||||
param_types.push(type_ref);
|
||||
}
|
||||
|
|
126
crates/hir-def/src/path/tests.rs
Normal file
126
crates/hir-def/src/path/tests.rs
Normal file
|
@ -0,0 +1,126 @@
|
|||
use expect_test::{expect, Expect};
|
||||
use span::Edition;
|
||||
use syntax::ast::{self, make};
|
||||
use test_fixture::WithFixture;
|
||||
|
||||
use crate::{
|
||||
lower::LowerCtx,
|
||||
path::{
|
||||
lower::{hir_segment_to_ast_segment, SEGMENT_LOWERING_MAP},
|
||||
Path,
|
||||
},
|
||||
pretty,
|
||||
test_db::TestDB,
|
||||
type_ref::{TypesMap, TypesSourceMap},
|
||||
};
|
||||
|
||||
fn lower_path(path: ast::Path) -> (TestDB, TypesMap, Option<Path>) {
|
||||
let (db, file_id) = TestDB::with_single_file("");
|
||||
let mut types_map = TypesMap::default();
|
||||
let mut types_source_map = TypesSourceMap::default();
|
||||
let mut ctx = LowerCtx::new(&db, file_id.into(), &mut types_map, &mut types_source_map);
|
||||
let lowered_path = ctx.lower_path(path);
|
||||
(db, types_map, lowered_path)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_hir_to_ast(path: &str, ignore_segments: &[&str]) {
|
||||
let path = make::path_from_text(path);
|
||||
SEGMENT_LOWERING_MAP.with_borrow_mut(|map| map.clear());
|
||||
let _ = lower_path(path.clone()).2.expect("failed to lower path");
|
||||
SEGMENT_LOWERING_MAP.with_borrow(|map| {
|
||||
for (segment, segment_idx) in map {
|
||||
if ignore_segments.contains(&&*segment.to_string()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let restored_segment = hir_segment_to_ast_segment(&path, *segment_idx as u32)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"failed to map back segment `{segment}` \
|
||||
numbered {segment_idx} in HIR from path `{path}`"
|
||||
)
|
||||
});
|
||||
assert_eq!(
|
||||
segment, &restored_segment,
|
||||
"mapping back `{segment}` numbered {segment_idx} in HIR \
|
||||
from path `{path}` produced incorrect segment `{restored_segment}`"
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hir_to_ast_trait_ref() {
|
||||
check_hir_to_ast("<A as B::C::D>::E::F", &["A"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hir_to_ast_plain_path() {
|
||||
check_hir_to_ast("A::B::C::D::E::F", &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hir_to_ast_crate_path() {
|
||||
check_hir_to_ast("crate::A::B::C", &[]);
|
||||
check_hir_to_ast("crate::super::super::A::B::C", &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hir_to_ast_self_path() {
|
||||
check_hir_to_ast("self::A::B::C", &[]);
|
||||
check_hir_to_ast("self::super::super::A::B::C", &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hir_to_ast_super_path() {
|
||||
check_hir_to_ast("super::A::B::C", &[]);
|
||||
check_hir_to_ast("super::super::super::A::B::C", &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hir_to_ast_type_anchor_path() {
|
||||
check_hir_to_ast("<A::B>::C::D", &["A", "B"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hir_to_ast_path_super_in_middle() {
|
||||
check_hir_to_ast("A::super::B::super::super::C::D", &[]);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_fail_lowering(path: &str) {
|
||||
let (_, _, lowered_path) = lower_path(make::path_from_text(path));
|
||||
assert!(lowered_path.is_none(), "path `{path}` should fail lowering");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keywords_in_middle_fail_lowering1() {
|
||||
check_fail_lowering("self::A::self::B::super::C::crate::D");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keywords_in_middle_fail_lowering2() {
|
||||
check_fail_lowering("A::super::self::C::D");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keywords_in_middle_fail_lowering3() {
|
||||
check_fail_lowering("A::crate::B::C::D");
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_path_lowering(path: &str, expected: Expect) {
|
||||
let (db, types_map, lowered_path) = lower_path(make::path_from_text(path));
|
||||
let lowered_path = lowered_path.expect("failed to lower path");
|
||||
let mut buf = String::new();
|
||||
pretty::print_path(&db, &lowered_path, &types_map, &mut buf, Edition::CURRENT)
|
||||
.expect("failed to pretty-print path");
|
||||
expected.assert_eq(&buf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_like_path_with_coloncolon() {
|
||||
check_path_lowering("Fn::(A, B) -> C", expect![[r#"Fn::<(A, B), Output = C>"#]]);
|
||||
check_path_lowering("Fn::(A, B)", expect![[r#"Fn::<(A, B), Output = ()>"#]]);
|
||||
}
|
|
@ -271,7 +271,7 @@ pub(crate) fn print_type_bounds(
|
|||
TraitBoundModifier::None => (),
|
||||
TraitBoundModifier::Maybe => write!(buf, "?")?,
|
||||
}
|
||||
print_path(db, path, map, buf, edition)?;
|
||||
print_path(db, &map[*path], map, buf, edition)?;
|
||||
}
|
||||
TypeBound::ForLifetime(lifetimes, path) => {
|
||||
write!(
|
||||
|
@ -279,7 +279,7 @@ pub(crate) fn print_type_bounds(
|
|||
"for<{}> ",
|
||||
lifetimes.iter().map(|it| it.display(db.upcast(), edition)).format(", ")
|
||||
)?;
|
||||
print_path(db, path, map, buf, edition)?;
|
||||
print_path(db, &map[*path], map, buf, edition)?;
|
||||
}
|
||||
TypeBound::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast(), edition))?,
|
||||
TypeBound::Use(args) => {
|
||||
|
|
|
@ -576,10 +576,12 @@ impl Resolver {
|
|||
match scope {
|
||||
Scope::BlockScope(m) => traits.extend(m.def_map[m.module_id].scope.traits()),
|
||||
&Scope::ImplDefScope(impl_) => {
|
||||
if let Some(target_trait) = &db.impl_data(impl_).target_trait {
|
||||
if let Some(TypeNs::TraitId(trait_)) =
|
||||
self.resolve_path_in_type_ns_fully(db, &target_trait.path)
|
||||
{
|
||||
let impl_data = db.impl_data(impl_);
|
||||
if let Some(target_trait) = impl_data.target_trait {
|
||||
if let Some(TypeNs::TraitId(trait_)) = self.resolve_path_in_type_ns_fully(
|
||||
db,
|
||||
&impl_data.types_map[target_trait.path],
|
||||
) {
|
||||
traits.insert(trait_);
|
||||
}
|
||||
}
|
||||
|
@ -640,9 +642,9 @@ impl Resolver {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn generic_params(&self) -> Option<&Arc<GenericParams>> {
|
||||
pub fn generic_params(&self) -> Option<&GenericParams> {
|
||||
self.scopes().find_map(|scope| match scope {
|
||||
Scope::GenericParams { params, .. } => Some(params),
|
||||
Scope::GenericParams { params, .. } => Some(&**params),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -78,6 +78,19 @@ impl FileLoader for TestDB {
|
|||
}
|
||||
|
||||
impl TestDB {
|
||||
pub(crate) fn fetch_test_crate(&self) -> CrateId {
|
||||
let crate_graph = self.crate_graph();
|
||||
let it = crate_graph
|
||||
.iter()
|
||||
.find(|&idx| {
|
||||
crate_graph[idx].display_name.as_ref().map(|it| it.canonical_name().as_str())
|
||||
== Some("ra_test_fixture")
|
||||
})
|
||||
.or_else(|| crate_graph.iter().next())
|
||||
.unwrap();
|
||||
it
|
||||
}
|
||||
|
||||
pub(crate) fn module_for_file(&self, file_id: FileId) -> ModuleId {
|
||||
for &krate in self.relevant_crates(file_id).iter() {
|
||||
let crate_def_map = self.crate_def_map(krate);
|
||||
|
|
|
@ -180,6 +180,13 @@ impl<FileId: FileIdToSyntax, T> InFileWrapper<FileId, T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(private_bounds)]
|
||||
impl<FileId: FileIdToSyntax, N: AstNode> InFileWrapper<FileId, AstPtr<N>> {
|
||||
pub fn to_node(&self, db: &dyn ExpandDatabase) -> N {
|
||||
self.value.to_node(&self.file_syntax(db))
|
||||
}
|
||||
}
|
||||
|
||||
impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, N> {
|
||||
pub fn syntax(&self) -> InFileWrapper<FileId, &SyntaxNode> {
|
||||
self.with_value(self.value.syntax())
|
||||
|
|
|
@ -110,7 +110,8 @@ pub(crate) fn fixup_syntax(
|
|||
}
|
||||
},
|
||||
ast::ExprStmt(it) => {
|
||||
if it.semicolon_token().is_none() {
|
||||
let needs_semi = it.semicolon_token().is_none() && it.expr().map_or(false, |e| e.syntax().kind() != SyntaxKind::BLOCK_EXPR);
|
||||
if needs_semi {
|
||||
append.insert(node.clone().into(), vec![
|
||||
Leaf::Punct(Punct {
|
||||
char: ';',
|
||||
|
@ -905,6 +906,21 @@ fn foo() {
|
|||
"#,
|
||||
expect![[r#"
|
||||
fn foo () {|| __ra_fixup}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixup_regression_() {
|
||||
check(
|
||||
r#"
|
||||
fn foo() {
|
||||
{}
|
||||
{}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn foo () {{} {}}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -269,6 +269,13 @@ pub enum MacroDefKind {
|
|||
ProcMacro(AstId<ast::Fn>, CustomProcMacroExpander, ProcMacroKind),
|
||||
}
|
||||
|
||||
impl MacroDefKind {
|
||||
#[inline]
|
||||
pub fn is_declarative(&self) -> bool {
|
||||
matches!(self, MacroDefKind::Declarative(..))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct EagerCallInfo {
|
||||
/// The expanded argument of the eager macro.
|
||||
|
|
|
@ -22,7 +22,7 @@ use crate::{
|
|||
consteval::ConstEvalError,
|
||||
dyn_compatibility::DynCompatibilityViolation,
|
||||
layout::{Layout, LayoutError},
|
||||
lower::{GenericDefaults, GenericPredicates},
|
||||
lower::{Diagnostics, GenericDefaults, GenericPredicates},
|
||||
method_resolution::{InherentImpls, TraitImpls, TyFingerprint},
|
||||
mir::{BorrowckResult, MirBody, MirLowerError},
|
||||
Binders, ClosureId, Const, FnDefId, ImplTraitId, ImplTraits, InferenceResult, Interner,
|
||||
|
@ -115,21 +115,35 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
|
|||
#[ra_salsa::cycle(crate::lower::ty_recover)]
|
||||
fn ty(&self, def: TyDefId) -> Binders<Ty>;
|
||||
|
||||
#[ra_salsa::invoke(crate::lower::type_for_type_alias_with_diagnostics_query)]
|
||||
fn type_for_type_alias_with_diagnostics(&self, def: TypeAliasId) -> (Binders<Ty>, Diagnostics);
|
||||
|
||||
/// Returns the type of the value of the given constant, or `None` if the `ValueTyDefId` is
|
||||
/// a `StructId` or `EnumVariantId` with a record constructor.
|
||||
#[ra_salsa::invoke(crate::lower::value_ty_query)]
|
||||
fn value_ty(&self, def: ValueTyDefId) -> Option<Binders<Ty>>;
|
||||
|
||||
#[ra_salsa::invoke(crate::lower::impl_self_ty_with_diagnostics_query)]
|
||||
#[ra_salsa::cycle(crate::lower::impl_self_ty_with_diagnostics_recover)]
|
||||
fn impl_self_ty_with_diagnostics(&self, def: ImplId) -> (Binders<Ty>, Diagnostics);
|
||||
#[ra_salsa::invoke(crate::lower::impl_self_ty_query)]
|
||||
#[ra_salsa::cycle(crate::lower::impl_self_ty_recover)]
|
||||
fn impl_self_ty(&self, def: ImplId) -> Binders<Ty>;
|
||||
|
||||
#[ra_salsa::invoke(crate::lower::const_param_ty_with_diagnostics_query)]
|
||||
fn const_param_ty_with_diagnostics(&self, def: ConstParamId) -> (Ty, Diagnostics);
|
||||
#[ra_salsa::invoke(crate::lower::const_param_ty_query)]
|
||||
fn const_param_ty(&self, def: ConstParamId) -> Ty;
|
||||
|
||||
#[ra_salsa::invoke(crate::lower::impl_trait_with_diagnostics_query)]
|
||||
fn impl_trait_with_diagnostics(&self, def: ImplId) -> Option<(Binders<TraitRef>, Diagnostics)>;
|
||||
#[ra_salsa::invoke(crate::lower::impl_trait_query)]
|
||||
fn impl_trait(&self, def: ImplId) -> Option<Binders<TraitRef>>;
|
||||
|
||||
#[ra_salsa::invoke(crate::lower::field_types_with_diagnostics_query)]
|
||||
fn field_types_with_diagnostics(
|
||||
&self,
|
||||
var: VariantId,
|
||||
) -> (Arc<ArenaMap<LocalFieldId, Binders<Ty>>>, Diagnostics);
|
||||
#[ra_salsa::invoke(crate::lower::field_types_query)]
|
||||
fn field_types(&self, var: VariantId) -> Arc<ArenaMap<LocalFieldId, Binders<Ty>>>;
|
||||
|
||||
|
@ -154,6 +168,11 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
|
|||
#[ra_salsa::invoke(crate::lower::generic_predicates_query)]
|
||||
fn generic_predicates(&self, def: GenericDefId) -> GenericPredicates;
|
||||
|
||||
#[ra_salsa::invoke(crate::lower::generic_predicates_without_parent_with_diagnostics_query)]
|
||||
fn generic_predicates_without_parent_with_diagnostics(
|
||||
&self,
|
||||
def: GenericDefId,
|
||||
) -> (GenericPredicates, Diagnostics);
|
||||
#[ra_salsa::invoke(crate::lower::generic_predicates_without_parent_query)]
|
||||
fn generic_predicates_without_parent(&self, def: GenericDefId) -> GenericPredicates;
|
||||
|
||||
|
@ -164,8 +183,13 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
|
|||
#[ra_salsa::invoke(crate::lower::trait_environment_query)]
|
||||
fn trait_environment(&self, def: GenericDefId) -> Arc<TraitEnvironment>;
|
||||
|
||||
#[ra_salsa::invoke(crate::lower::generic_defaults_with_diagnostics_query)]
|
||||
#[ra_salsa::cycle(crate::lower::generic_defaults_with_diagnostics_recover)]
|
||||
fn generic_defaults_with_diagnostics(
|
||||
&self,
|
||||
def: GenericDefId,
|
||||
) -> (GenericDefaults, Diagnostics);
|
||||
#[ra_salsa::invoke(crate::lower::generic_defaults_query)]
|
||||
#[ra_salsa::cycle(crate::lower::generic_defaults_recover)]
|
||||
fn generic_defaults(&self, def: GenericDefId) -> GenericDefaults;
|
||||
|
||||
#[ra_salsa::invoke(InherentImpls::inherent_impls_in_crate_query)]
|
||||
|
|
|
@ -9,5 +9,5 @@ pub use crate::diagnostics::{
|
|||
expr::{
|
||||
record_literal_missing_fields, record_pattern_missing_fields, BodyValidationDiagnostic,
|
||||
},
|
||||
unsafe_check::{missing_unsafe, unsafe_expressions, UnsafeExpr},
|
||||
unsafe_check::{missing_unsafe, unsafe_expressions, InsideUnsafeBlock, UnsafetyReason},
|
||||
};
|
||||
|
|
|
@ -383,9 +383,6 @@ impl<'db> PatCx for MatchCheckCtx<'db> {
|
|||
} else {
|
||||
let variant = Self::variant_id_for_adt(self.db, ctor, adt).unwrap();
|
||||
|
||||
// Whether we must not match the fields of this variant exhaustively.
|
||||
let is_non_exhaustive =
|
||||
LazyCell::new(|| self.is_foreign_non_exhaustive(adt));
|
||||
let visibilities = LazyCell::new(|| self.db.field_visibilities(variant));
|
||||
|
||||
self.list_variant_fields(ty, variant)
|
||||
|
@ -396,8 +393,7 @@ impl<'db> PatCx for MatchCheckCtx<'db> {
|
|||
.is_visible_from(self.db.upcast(), self.module)
|
||||
};
|
||||
let is_uninhabited = self.is_uninhabited(&ty);
|
||||
let private_uninhabited =
|
||||
is_uninhabited && (!is_visible() || *is_non_exhaustive);
|
||||
let private_uninhabited = is_uninhabited && !is_visible();
|
||||
(ty, PrivateUninhabitedField(private_uninhabited))
|
||||
})
|
||||
.collect()
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
//! Provides validations for unsafe code. Currently checks if unsafe functions are missing
|
||||
//! unsafe blocks.
|
||||
|
||||
use std::mem;
|
||||
|
||||
use either::Either;
|
||||
use hir_def::{
|
||||
body::Body,
|
||||
hir::{Expr, ExprId, ExprOrPatId, Pat, UnaryOp},
|
||||
resolver::{resolver_for_expr, ResolveValueResult, Resolver, ValueNs},
|
||||
hir::{Expr, ExprId, ExprOrPatId, Pat, PatId, Statement, UnaryOp},
|
||||
path::Path,
|
||||
resolver::{HasResolver, ResolveValueResult, Resolver, ValueNs},
|
||||
type_ref::Rawness,
|
||||
DefWithBodyId,
|
||||
AdtId, DefWithBodyId, FieldId, VariantId,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
@ -16,7 +20,10 @@ use crate::{
|
|||
/// Returns `(unsafe_exprs, fn_is_unsafe)`.
|
||||
///
|
||||
/// If `fn_is_unsafe` is false, `unsafe_exprs` are hard errors. If true, they're `unsafe_op_in_unsafe_fn`.
|
||||
pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> (Vec<ExprOrPatId>, bool) {
|
||||
pub fn missing_unsafe(
|
||||
db: &dyn HirDatabase,
|
||||
def: DefWithBodyId,
|
||||
) -> (Vec<(ExprOrPatId, UnsafetyReason)>, bool) {
|
||||
let _p = tracing::info_span!("missing_unsafe").entered();
|
||||
|
||||
let mut res = Vec::new();
|
||||
|
@ -30,111 +37,243 @@ pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> (Vec<ExprOrPa
|
|||
|
||||
let body = db.body(def);
|
||||
let infer = db.infer(def);
|
||||
unsafe_expressions(db, &infer, def, &body, body.body_expr, &mut |expr| {
|
||||
if !expr.inside_unsafe_block {
|
||||
res.push(expr.node);
|
||||
let mut callback = |node, inside_unsafe_block, reason| {
|
||||
if inside_unsafe_block == InsideUnsafeBlock::No {
|
||||
res.push((node, reason));
|
||||
}
|
||||
});
|
||||
};
|
||||
let mut visitor = UnsafeVisitor::new(db, &infer, &body, def, &mut callback);
|
||||
visitor.walk_expr(body.body_expr);
|
||||
|
||||
if !is_unsafe {
|
||||
// Unsafety in function parameter patterns (that can only be union destructuring)
|
||||
// cannot be inserted into an unsafe block, so even with `unsafe_op_in_unsafe_fn`
|
||||
// it is turned off for unsafe functions.
|
||||
for ¶m in &body.params {
|
||||
visitor.walk_pat(param);
|
||||
}
|
||||
}
|
||||
|
||||
(res, is_unsafe)
|
||||
}
|
||||
|
||||
pub struct UnsafeExpr {
|
||||
pub node: ExprOrPatId,
|
||||
pub inside_unsafe_block: bool,
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum UnsafetyReason {
|
||||
UnionField,
|
||||
UnsafeFnCall,
|
||||
InlineAsm,
|
||||
RawPtrDeref,
|
||||
MutableStatic,
|
||||
ExternStatic,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum InsideUnsafeBlock {
|
||||
No,
|
||||
Yes,
|
||||
}
|
||||
|
||||
// FIXME: Move this out, its not a diagnostic only thing anymore, and handle unsafe pattern accesses as well
|
||||
pub fn unsafe_expressions(
|
||||
db: &dyn HirDatabase,
|
||||
infer: &InferenceResult,
|
||||
def: DefWithBodyId,
|
||||
body: &Body,
|
||||
current: ExprId,
|
||||
unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr),
|
||||
unsafe_expr_cb: &mut dyn FnMut(ExprOrPatId, InsideUnsafeBlock, UnsafetyReason),
|
||||
) {
|
||||
walk_unsafe(
|
||||
db,
|
||||
infer,
|
||||
body,
|
||||
&mut resolver_for_expr(db.upcast(), def, current),
|
||||
def,
|
||||
current,
|
||||
false,
|
||||
unsafe_expr_cb,
|
||||
)
|
||||
let mut visitor = UnsafeVisitor::new(db, infer, body, def, unsafe_expr_cb);
|
||||
_ = visitor.resolver.update_to_inner_scope(db.upcast(), def, current);
|
||||
visitor.walk_expr(current);
|
||||
}
|
||||
|
||||
fn walk_unsafe(
|
||||
db: &dyn HirDatabase,
|
||||
infer: &InferenceResult,
|
||||
body: &Body,
|
||||
resolver: &mut Resolver,
|
||||
struct UnsafeVisitor<'a> {
|
||||
db: &'a dyn HirDatabase,
|
||||
infer: &'a InferenceResult,
|
||||
body: &'a Body,
|
||||
resolver: Resolver,
|
||||
def: DefWithBodyId,
|
||||
current: ExprId,
|
||||
inside_unsafe_block: bool,
|
||||
unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr),
|
||||
) {
|
||||
let mut mark_unsafe_path = |path, node| {
|
||||
let g = resolver.update_to_inner_scope(db.upcast(), def, current);
|
||||
let hygiene = body.expr_or_pat_path_hygiene(node);
|
||||
let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path, hygiene);
|
||||
if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial {
|
||||
let static_data = db.static_data(id);
|
||||
if static_data.mutable || (static_data.is_extern && !static_data.has_safe_kw) {
|
||||
unsafe_expr_cb(UnsafeExpr { node, inside_unsafe_block });
|
||||
}
|
||||
}
|
||||
resolver.reset_to_guard(g);
|
||||
};
|
||||
inside_unsafe_block: InsideUnsafeBlock,
|
||||
inside_assignment: bool,
|
||||
inside_union_destructure: bool,
|
||||
unsafe_expr_cb: &'a mut dyn FnMut(ExprOrPatId, InsideUnsafeBlock, UnsafetyReason),
|
||||
}
|
||||
|
||||
let expr = &body.exprs[current];
|
||||
match expr {
|
||||
&Expr::Call { callee, .. } => {
|
||||
if let Some(func) = infer[callee].as_fn_def(db) {
|
||||
if is_fn_unsafe_to_call(db, func) {
|
||||
unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block });
|
||||
}
|
||||
}
|
||||
impl<'a> UnsafeVisitor<'a> {
|
||||
fn new(
|
||||
db: &'a dyn HirDatabase,
|
||||
infer: &'a InferenceResult,
|
||||
body: &'a Body,
|
||||
def: DefWithBodyId,
|
||||
unsafe_expr_cb: &'a mut dyn FnMut(ExprOrPatId, InsideUnsafeBlock, UnsafetyReason),
|
||||
) -> Self {
|
||||
let resolver = def.resolver(db.upcast());
|
||||
Self {
|
||||
db,
|
||||
infer,
|
||||
body,
|
||||
resolver,
|
||||
def,
|
||||
inside_unsafe_block: InsideUnsafeBlock::No,
|
||||
inside_assignment: false,
|
||||
inside_union_destructure: false,
|
||||
unsafe_expr_cb,
|
||||
}
|
||||
Expr::Path(path) => mark_unsafe_path(path, current.into()),
|
||||
Expr::Ref { expr, rawness: Rawness::RawPtr, mutability: _ } => {
|
||||
if let Expr::Path(_) = body.exprs[*expr] {
|
||||
// Do not report unsafe for `addr_of[_mut]!(EXTERN_OR_MUT_STATIC)`,
|
||||
// see https://github.com/rust-lang/rust/pull/125834.
|
||||
return;
|
||||
}
|
||||
}
|
||||
Expr::MethodCall { .. } => {
|
||||
if infer
|
||||
.method_resolution(current)
|
||||
.map(|(func, _)| is_fn_unsafe_to_call(db, func))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block });
|
||||
}
|
||||
}
|
||||
Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
|
||||
if let TyKind::Raw(..) = &infer[*expr].kind(Interner) {
|
||||
unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block });
|
||||
}
|
||||
}
|
||||
Expr::Unsafe { .. } => {
|
||||
return body.walk_child_exprs(current, |child| {
|
||||
walk_unsafe(db, infer, body, resolver, def, child, true, unsafe_expr_cb);
|
||||
});
|
||||
}
|
||||
&Expr::Assignment { target, value: _ } => {
|
||||
body.walk_pats(target, &mut |pat| {
|
||||
if let Pat::Path(path) = &body[pat] {
|
||||
mark_unsafe_path(path, pat.into());
|
||||
}
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
body.walk_child_exprs(current, |child| {
|
||||
walk_unsafe(db, infer, body, resolver, def, child, inside_unsafe_block, unsafe_expr_cb);
|
||||
});
|
||||
fn call_cb(&mut self, node: ExprOrPatId, reason: UnsafetyReason) {
|
||||
(self.unsafe_expr_cb)(node, self.inside_unsafe_block, reason);
|
||||
}
|
||||
|
||||
fn walk_pats_top(&mut self, pats: impl Iterator<Item = PatId>, parent_expr: ExprId) {
|
||||
let guard = self.resolver.update_to_inner_scope(self.db.upcast(), self.def, parent_expr);
|
||||
pats.for_each(|pat| self.walk_pat(pat));
|
||||
self.resolver.reset_to_guard(guard);
|
||||
}
|
||||
|
||||
fn walk_pat(&mut self, current: PatId) {
|
||||
let pat = &self.body.pats[current];
|
||||
|
||||
if self.inside_union_destructure {
|
||||
match pat {
|
||||
Pat::Tuple { .. }
|
||||
| Pat::Record { .. }
|
||||
| Pat::Range { .. }
|
||||
| Pat::Slice { .. }
|
||||
| Pat::Path(..)
|
||||
| Pat::Lit(..)
|
||||
| Pat::Bind { .. }
|
||||
| Pat::TupleStruct { .. }
|
||||
| Pat::Ref { .. }
|
||||
| Pat::Box { .. }
|
||||
| Pat::Expr(..)
|
||||
| Pat::ConstBlock(..) => self.call_cb(current.into(), UnsafetyReason::UnionField),
|
||||
// `Or` only wraps other patterns, and `Missing`/`Wild` do not constitute a read.
|
||||
Pat::Missing | Pat::Wild | Pat::Or(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
match pat {
|
||||
Pat::Record { .. } => {
|
||||
if let Some((AdtId::UnionId(_), _)) = self.infer[current].as_adt() {
|
||||
let old_inside_union_destructure =
|
||||
mem::replace(&mut self.inside_union_destructure, true);
|
||||
self.body.walk_pats_shallow(current, |pat| self.walk_pat(pat));
|
||||
self.inside_union_destructure = old_inside_union_destructure;
|
||||
return;
|
||||
}
|
||||
}
|
||||
Pat::Path(path) => self.mark_unsafe_path(current.into(), path),
|
||||
&Pat::ConstBlock(expr) => {
|
||||
let old_inside_assignment = mem::replace(&mut self.inside_assignment, false);
|
||||
self.walk_expr(expr);
|
||||
self.inside_assignment = old_inside_assignment;
|
||||
}
|
||||
&Pat::Expr(expr) => self.walk_expr(expr),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.body.walk_pats_shallow(current, |pat| self.walk_pat(pat));
|
||||
}
|
||||
|
||||
fn walk_expr(&mut self, current: ExprId) {
|
||||
let expr = &self.body.exprs[current];
|
||||
let inside_assignment = mem::replace(&mut self.inside_assignment, false);
|
||||
match expr {
|
||||
&Expr::Call { callee, .. } => {
|
||||
if let Some(func) = self.infer[callee].as_fn_def(self.db) {
|
||||
if is_fn_unsafe_to_call(self.db, func) {
|
||||
self.call_cb(current.into(), UnsafetyReason::UnsafeFnCall);
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Path(path) => {
|
||||
let guard =
|
||||
self.resolver.update_to_inner_scope(self.db.upcast(), self.def, current);
|
||||
self.mark_unsafe_path(current.into(), path);
|
||||
self.resolver.reset_to_guard(guard);
|
||||
}
|
||||
Expr::Ref { expr, rawness: Rawness::RawPtr, mutability: _ } => {
|
||||
if let Expr::Path(_) = self.body.exprs[*expr] {
|
||||
// Do not report unsafe for `addr_of[_mut]!(EXTERN_OR_MUT_STATIC)`,
|
||||
// see https://github.com/rust-lang/rust/pull/125834.
|
||||
return;
|
||||
}
|
||||
}
|
||||
Expr::MethodCall { .. } => {
|
||||
if self
|
||||
.infer
|
||||
.method_resolution(current)
|
||||
.map(|(func, _)| is_fn_unsafe_to_call(self.db, func))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
self.call_cb(current.into(), UnsafetyReason::UnsafeFnCall);
|
||||
}
|
||||
}
|
||||
Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
|
||||
if let TyKind::Raw(..) = &self.infer[*expr].kind(Interner) {
|
||||
self.call_cb(current.into(), UnsafetyReason::RawPtrDeref);
|
||||
}
|
||||
}
|
||||
Expr::Unsafe { .. } => {
|
||||
let old_inside_unsafe_block =
|
||||
mem::replace(&mut self.inside_unsafe_block, InsideUnsafeBlock::Yes);
|
||||
self.body.walk_child_exprs_without_pats(current, |child| self.walk_expr(child));
|
||||
self.inside_unsafe_block = old_inside_unsafe_block;
|
||||
return;
|
||||
}
|
||||
&Expr::Assignment { target, value: _ } => {
|
||||
let old_inside_assignment = mem::replace(&mut self.inside_assignment, true);
|
||||
self.walk_pats_top(std::iter::once(target), current);
|
||||
self.inside_assignment = old_inside_assignment;
|
||||
}
|
||||
Expr::InlineAsm(_) => self.call_cb(current.into(), UnsafetyReason::InlineAsm),
|
||||
// rustc allows union assignment to propagate through field accesses and casts.
|
||||
Expr::Cast { .. } => self.inside_assignment = inside_assignment,
|
||||
Expr::Field { .. } => {
|
||||
self.inside_assignment = inside_assignment;
|
||||
if !inside_assignment {
|
||||
if let Some(Either::Left(FieldId { parent: VariantId::UnionId(_), .. })) =
|
||||
self.infer.field_resolution(current)
|
||||
{
|
||||
self.call_cb(current.into(), UnsafetyReason::UnionField);
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Block { statements, .. } | Expr::Async { statements, .. } => {
|
||||
self.walk_pats_top(
|
||||
statements.iter().filter_map(|statement| match statement {
|
||||
&Statement::Let { pat, .. } => Some(pat),
|
||||
_ => None,
|
||||
}),
|
||||
current,
|
||||
);
|
||||
}
|
||||
Expr::Match { arms, .. } => {
|
||||
self.walk_pats_top(arms.iter().map(|arm| arm.pat), current);
|
||||
}
|
||||
&Expr::Let { pat, .. } => {
|
||||
self.walk_pats_top(std::iter::once(pat), current);
|
||||
}
|
||||
Expr::Closure { args, .. } => {
|
||||
self.walk_pats_top(args.iter().copied(), current);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.body.walk_child_exprs_without_pats(current, |child| self.walk_expr(child));
|
||||
}
|
||||
|
||||
fn mark_unsafe_path(&mut self, node: ExprOrPatId, path: &Path) {
|
||||
let hygiene = self.body.expr_or_pat_path_hygiene(node);
|
||||
let value_or_partial =
|
||||
self.resolver.resolve_path_in_value_ns(self.db.upcast(), path, hygiene);
|
||||
if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial {
|
||||
let static_data = self.db.static_data(id);
|
||||
if static_data.mutable {
|
||||
self.call_cb(node, UnsafetyReason::MutableStatic);
|
||||
} else if static_data.is_extern && !static_data.has_safe_kw {
|
||||
self.call_cb(node, UnsafetyReason::ExternStatic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1047,10 +1047,14 @@ impl HirDisplay for Ty {
|
|||
);
|
||||
// We print all params except implicit impl Trait params. Still a bit weird; should we leave out parent and self?
|
||||
if parameters.len() - impl_ > 0 {
|
||||
let params_len = parameters.len();
|
||||
// `parameters` are in the order of fn's params (including impl traits), fn's lifetimes
|
||||
let parameters =
|
||||
generic_args_sans_defaults(f, Some(generic_def_id), parameters);
|
||||
let without_impl = self_param as usize + type_ + const_ + lifetime;
|
||||
assert!(params_len >= parameters.len());
|
||||
let defaults = params_len - parameters.len();
|
||||
let without_impl =
|
||||
self_param as usize + type_ + const_ + lifetime - defaults;
|
||||
// parent's params (those from enclosing impl or trait, if any).
|
||||
let (fn_params, parent_params) = parameters.split_at(without_impl + impl_);
|
||||
|
||||
|
@ -2062,12 +2066,12 @@ impl HirDisplayWithTypesMap for TypeBound {
|
|||
types_map: &TypesMap,
|
||||
) -> Result<(), HirDisplayError> {
|
||||
match self {
|
||||
TypeBound::Path(path, modifier) => {
|
||||
&TypeBound::Path(path, modifier) => {
|
||||
match modifier {
|
||||
TraitBoundModifier::None => (),
|
||||
TraitBoundModifier::Maybe => write!(f, "?")?,
|
||||
}
|
||||
path.hir_fmt(f, types_map)
|
||||
types_map[path].hir_fmt(f, types_map)
|
||||
}
|
||||
TypeBound::Lifetime(lifetime) => {
|
||||
write!(f, "{}", lifetime.name.display(f.db.upcast(), f.edition()))
|
||||
|
@ -2079,7 +2083,7 @@ impl HirDisplayWithTypesMap for TypeBound {
|
|||
"for<{}> ",
|
||||
lifetimes.iter().map(|it| it.display(f.db.upcast(), edition)).format(", ")
|
||||
)?;
|
||||
path.hir_fmt(f, types_map)
|
||||
types_map[*path].hir_fmt(f, types_map)
|
||||
}
|
||||
TypeBound::Use(args) => {
|
||||
let edition = f.edition();
|
||||
|
|
|
@ -55,6 +55,10 @@ impl Generics {
|
|||
self.def
|
||||
}
|
||||
|
||||
pub(crate) fn self_types_map(&self) -> &TypesMap {
|
||||
&self.params.types_map
|
||||
}
|
||||
|
||||
pub(crate) fn iter_id(&self) -> impl Iterator<Item = GenericParamId> + '_ {
|
||||
self.iter_self_id().chain(self.iter_parent_id())
|
||||
}
|
||||
|
@ -86,15 +90,13 @@ impl Generics {
|
|||
self.iter_self().chain(self.iter_parent())
|
||||
}
|
||||
|
||||
pub(crate) fn iter_with_types_map(
|
||||
pub(crate) fn iter_parents_with_types_map(
|
||||
&self,
|
||||
) -> impl Iterator<Item = ((GenericParamId, GenericParamDataRef<'_>), &TypesMap)> + '_ {
|
||||
self.iter_self().zip(std::iter::repeat(&self.params.types_map)).chain(
|
||||
self.iter_parent().zip(
|
||||
self.parent_generics()
|
||||
.into_iter()
|
||||
.flat_map(|it| std::iter::repeat(&it.params.types_map)),
|
||||
),
|
||||
self.iter_parent().zip(
|
||||
self.parent_generics()
|
||||
.into_iter()
|
||||
.flat_map(|it| std::iter::repeat(&it.params.types_map)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ use crate::{
|
|||
fold_tys,
|
||||
generics::Generics,
|
||||
infer::{coerce::CoerceMany, expr::ExprIsRead, unify::InferenceTable},
|
||||
lower::ImplTraitLoweringMode,
|
||||
lower::{ImplTraitLoweringMode, TyLoweringDiagnostic},
|
||||
mir::MirSpan,
|
||||
to_assoc_type_id,
|
||||
traits::FnTrait,
|
||||
|
@ -191,6 +191,14 @@ impl<T> InferOk<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum InferenceTyDiagnosticSource {
|
||||
/// Diagnostics that come from types in the body.
|
||||
Body,
|
||||
/// Diagnostics that come from types in fn parameters/return type, or static & const types.
|
||||
Signature,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TypeError;
|
||||
pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>;
|
||||
|
@ -264,6 +272,10 @@ pub enum InferenceDiagnostic {
|
|||
expr_ty: Ty,
|
||||
cast_ty: Ty,
|
||||
},
|
||||
TyDiagnostic {
|
||||
source: InferenceTyDiagnosticSource,
|
||||
diag: TyLoweringDiagnostic,
|
||||
},
|
||||
}
|
||||
|
||||
/// A mismatch between an expected and an inferred type.
|
||||
|
@ -858,7 +870,8 @@ impl<'a> InferenceContext<'a> {
|
|||
}
|
||||
|
||||
fn collect_const(&mut self, data: &ConstData) {
|
||||
let return_ty = self.make_ty(data.type_ref, &data.types_map);
|
||||
let return_ty =
|
||||
self.make_ty(data.type_ref, &data.types_map, InferenceTyDiagnosticSource::Signature);
|
||||
|
||||
// Constants might be defining usage sites of TAITs.
|
||||
self.make_tait_coercion_table(iter::once(&return_ty));
|
||||
|
@ -867,7 +880,8 @@ impl<'a> InferenceContext<'a> {
|
|||
}
|
||||
|
||||
fn collect_static(&mut self, data: &StaticData) {
|
||||
let return_ty = self.make_ty(data.type_ref, &data.types_map);
|
||||
let return_ty =
|
||||
self.make_ty(data.type_ref, &data.types_map, InferenceTyDiagnosticSource::Signature);
|
||||
|
||||
// Statics might be defining usage sites of TAITs.
|
||||
self.make_tait_coercion_table(iter::once(&return_ty));
|
||||
|
@ -877,11 +891,12 @@ impl<'a> InferenceContext<'a> {
|
|||
|
||||
fn collect_fn(&mut self, func: FunctionId) {
|
||||
let data = self.db.function_data(func);
|
||||
let mut param_tys = self.with_ty_lowering(&data.types_map, |ctx| {
|
||||
ctx.type_param_mode(ParamLoweringMode::Placeholder)
|
||||
.impl_trait_mode(ImplTraitLoweringMode::Param);
|
||||
data.params.iter().map(|&type_ref| ctx.lower_ty(type_ref)).collect::<Vec<_>>()
|
||||
});
|
||||
let mut param_tys =
|
||||
self.with_ty_lowering(&data.types_map, InferenceTyDiagnosticSource::Signature, |ctx| {
|
||||
ctx.type_param_mode(ParamLoweringMode::Placeholder)
|
||||
.impl_trait_mode(ImplTraitLoweringMode::Param);
|
||||
data.params.iter().map(|&type_ref| ctx.lower_ty(type_ref)).collect::<Vec<_>>()
|
||||
});
|
||||
// Check if function contains a va_list, if it does then we append it to the parameter types
|
||||
// that are collected from the function data
|
||||
if data.is_varargs() {
|
||||
|
@ -918,11 +933,12 @@ impl<'a> InferenceContext<'a> {
|
|||
}
|
||||
let return_ty = data.ret_type;
|
||||
|
||||
let return_ty = self.with_ty_lowering(&data.types_map, |ctx| {
|
||||
ctx.type_param_mode(ParamLoweringMode::Placeholder)
|
||||
.impl_trait_mode(ImplTraitLoweringMode::Opaque)
|
||||
.lower_ty(return_ty)
|
||||
});
|
||||
let return_ty =
|
||||
self.with_ty_lowering(&data.types_map, InferenceTyDiagnosticSource::Signature, |ctx| {
|
||||
ctx.type_param_mode(ParamLoweringMode::Placeholder)
|
||||
.impl_trait_mode(ImplTraitLoweringMode::Opaque)
|
||||
.lower_ty(return_ty)
|
||||
});
|
||||
let return_ty = self.insert_type_vars(return_ty);
|
||||
|
||||
let return_ty = if let Some(rpits) = self.db.return_type_impl_traits(func) {
|
||||
|
@ -1226,9 +1242,20 @@ impl<'a> InferenceContext<'a> {
|
|||
self.result.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
fn push_ty_diagnostics(
|
||||
&mut self,
|
||||
source: InferenceTyDiagnosticSource,
|
||||
diagnostics: Vec<TyLoweringDiagnostic>,
|
||||
) {
|
||||
self.result.diagnostics.extend(
|
||||
diagnostics.into_iter().map(|diag| InferenceDiagnostic::TyDiagnostic { source, diag }),
|
||||
);
|
||||
}
|
||||
|
||||
fn with_ty_lowering<R>(
|
||||
&self,
|
||||
&mut self,
|
||||
types_map: &TypesMap,
|
||||
types_source: InferenceTyDiagnosticSource,
|
||||
f: impl FnOnce(&mut crate::lower::TyLoweringContext<'_>) -> R,
|
||||
) -> R {
|
||||
let mut ctx = crate::lower::TyLoweringContext::new(
|
||||
|
@ -1237,32 +1264,41 @@ impl<'a> InferenceContext<'a> {
|
|||
types_map,
|
||||
self.owner.into(),
|
||||
);
|
||||
f(&mut ctx)
|
||||
let result = f(&mut ctx);
|
||||
self.push_ty_diagnostics(types_source, ctx.diagnostics);
|
||||
result
|
||||
}
|
||||
|
||||
fn with_body_ty_lowering<R>(
|
||||
&self,
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut crate::lower::TyLoweringContext<'_>) -> R,
|
||||
) -> R {
|
||||
self.with_ty_lowering(&self.body.types, f)
|
||||
self.with_ty_lowering(&self.body.types, InferenceTyDiagnosticSource::Body, f)
|
||||
}
|
||||
|
||||
fn make_ty(&mut self, type_ref: TypeRefId, types_map: &TypesMap) -> Ty {
|
||||
let ty = self.with_ty_lowering(types_map, |ctx| ctx.lower_ty(type_ref));
|
||||
fn make_ty(
|
||||
&mut self,
|
||||
type_ref: TypeRefId,
|
||||
types_map: &TypesMap,
|
||||
type_source: InferenceTyDiagnosticSource,
|
||||
) -> Ty {
|
||||
let ty = self.with_ty_lowering(types_map, type_source, |ctx| ctx.lower_ty(type_ref));
|
||||
let ty = self.insert_type_vars(ty);
|
||||
self.normalize_associated_types_in(ty)
|
||||
}
|
||||
|
||||
fn make_body_ty(&mut self, type_ref: TypeRefId) -> Ty {
|
||||
self.make_ty(type_ref, &self.body.types)
|
||||
self.make_ty(type_ref, &self.body.types, InferenceTyDiagnosticSource::Body)
|
||||
}
|
||||
|
||||
fn err_ty(&self) -> Ty {
|
||||
self.result.standard_types.unknown.clone()
|
||||
}
|
||||
|
||||
fn make_lifetime(&mut self, lifetime_ref: &LifetimeRef) -> Lifetime {
|
||||
let lt = self.with_ty_lowering(TypesMap::EMPTY, |ctx| ctx.lower_lifetime(lifetime_ref));
|
||||
fn make_body_lifetime(&mut self, lifetime_ref: &LifetimeRef) -> Lifetime {
|
||||
let lt = self.with_ty_lowering(TypesMap::EMPTY, InferenceTyDiagnosticSource::Body, |ctx| {
|
||||
ctx.lower_lifetime(lifetime_ref)
|
||||
});
|
||||
self.insert_type_vars(lt)
|
||||
}
|
||||
|
||||
|
@ -1431,12 +1467,20 @@ impl<'a> InferenceContext<'a> {
|
|||
Some(ResolveValueResult::ValueNs(value, _)) => match value {
|
||||
ValueNs::EnumVariantId(var) => {
|
||||
let substs = ctx.substs_from_path(path, var.into(), true);
|
||||
self.push_ty_diagnostics(
|
||||
InferenceTyDiagnosticSource::Body,
|
||||
ctx.diagnostics,
|
||||
);
|
||||
let ty = self.db.ty(var.lookup(self.db.upcast()).parent.into());
|
||||
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
|
||||
return (ty, Some(var.into()));
|
||||
}
|
||||
ValueNs::StructId(strukt) => {
|
||||
let substs = ctx.substs_from_path(path, strukt.into(), true);
|
||||
self.push_ty_diagnostics(
|
||||
InferenceTyDiagnosticSource::Body,
|
||||
ctx.diagnostics,
|
||||
);
|
||||
let ty = self.db.ty(strukt.into());
|
||||
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
|
||||
return (ty, Some(strukt.into()));
|
||||
|
@ -1462,18 +1506,21 @@ impl<'a> InferenceContext<'a> {
|
|||
return match resolution {
|
||||
TypeNs::AdtId(AdtId::StructId(strukt)) => {
|
||||
let substs = ctx.substs_from_path(path, strukt.into(), true);
|
||||
self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics);
|
||||
let ty = self.db.ty(strukt.into());
|
||||
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
|
||||
forbid_unresolved_segments((ty, Some(strukt.into())), unresolved)
|
||||
}
|
||||
TypeNs::AdtId(AdtId::UnionId(u)) => {
|
||||
let substs = ctx.substs_from_path(path, u.into(), true);
|
||||
self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics);
|
||||
let ty = self.db.ty(u.into());
|
||||
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
|
||||
forbid_unresolved_segments((ty, Some(u.into())), unresolved)
|
||||
}
|
||||
TypeNs::EnumVariantId(var) => {
|
||||
let substs = ctx.substs_from_path(path, var.into(), true);
|
||||
self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics);
|
||||
let ty = self.db.ty(var.lookup(self.db.upcast()).parent.into());
|
||||
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
|
||||
forbid_unresolved_segments((ty, Some(var.into())), unresolved)
|
||||
|
@ -1519,6 +1566,9 @@ impl<'a> InferenceContext<'a> {
|
|||
resolved_segment,
|
||||
current_segment,
|
||||
false,
|
||||
&mut |_, _reason| {
|
||||
// FIXME: Report an error.
|
||||
},
|
||||
);
|
||||
|
||||
ty = self.table.insert_type_vars(ty);
|
||||
|
@ -1532,6 +1582,7 @@ impl<'a> InferenceContext<'a> {
|
|||
remaining_idx += 1;
|
||||
remaining_segments = remaining_segments.skip(1);
|
||||
}
|
||||
self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics);
|
||||
|
||||
let variant = ty.as_adt().and_then(|(id, _)| match id {
|
||||
AdtId::StructId(s) => Some(VariantId::StructId(s)),
|
||||
|
@ -1550,6 +1601,7 @@ impl<'a> InferenceContext<'a> {
|
|||
};
|
||||
let substs =
|
||||
ctx.substs_from_path_segment(resolved_seg, Some(it.into()), true, None);
|
||||
self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics);
|
||||
let ty = self.db.ty(it.into());
|
||||
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
|
||||
|
||||
|
|
|
@ -125,7 +125,11 @@ impl CoerceMany {
|
|||
// pointers to have a chance at getting a match. See
|
||||
// https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916
|
||||
let sig = match (self.merged_ty().kind(Interner), expr_ty.kind(Interner)) {
|
||||
(TyKind::FnDef(x, _), TyKind::FnDef(y, _)) if x == y => None,
|
||||
(TyKind::FnDef(x, _), TyKind::FnDef(y, _))
|
||||
if x == y && ctx.table.unify(&self.merged_ty(), &expr_ty) =>
|
||||
{
|
||||
None
|
||||
}
|
||||
(TyKind::Closure(x, _), TyKind::Closure(y, _)) if x == y => None,
|
||||
(TyKind::FnDef(..) | TyKind::Closure(..), TyKind::FnDef(..) | TyKind::Closure(..)) => {
|
||||
// FIXME: we're ignoring safety here. To be more correct, if we have one FnDef and one Closure,
|
||||
|
|
|
@ -1287,8 +1287,8 @@ impl InferenceContext<'_> {
|
|||
tgt_expr: ExprId,
|
||||
) {
|
||||
match fn_x {
|
||||
FnTrait::FnOnce => (),
|
||||
FnTrait::FnMut => {
|
||||
FnTrait::FnOnce | FnTrait::AsyncFnOnce => (),
|
||||
FnTrait::FnMut | FnTrait::AsyncFnMut => {
|
||||
if let TyKind::Ref(Mutability::Mut, lt, inner) = derefed_callee.kind(Interner) {
|
||||
if adjustments
|
||||
.last()
|
||||
|
@ -1312,7 +1312,7 @@ impl InferenceContext<'_> {
|
|||
));
|
||||
}
|
||||
}
|
||||
FnTrait::Fn => {
|
||||
FnTrait::Fn | FnTrait::AsyncFn => {
|
||||
if !matches!(derefed_callee.kind(Interner), TyKind::Ref(Mutability::Not, _, _)) {
|
||||
adjustments.push(Adjustment::borrow(
|
||||
Mutability::Not,
|
||||
|
@ -2155,7 +2155,7 @@ impl InferenceContext<'_> {
|
|||
DebruijnIndex::INNERMOST,
|
||||
)
|
||||
},
|
||||
|this, lt_ref| this.make_lifetime(lt_ref),
|
||||
|this, lt_ref| this.make_body_lifetime(lt_ref),
|
||||
),
|
||||
};
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ use crate::{
|
|||
TyBuilder, TyExt, TyKind, ValueTyDefId,
|
||||
};
|
||||
|
||||
use super::{ExprOrPatId, InferenceContext};
|
||||
use super::{ExprOrPatId, InferenceContext, InferenceTyDiagnosticSource};
|
||||
|
||||
impl InferenceContext<'_> {
|
||||
pub(super) fn infer_path(&mut self, path: &Path, id: ExprOrPatId) -> Option<Ty> {
|
||||
|
@ -163,6 +163,7 @@ impl InferenceContext<'_> {
|
|||
|
||||
let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1);
|
||||
let (ty, _) = ctx.lower_ty_relative_path(ty, orig_ns, remaining_segments_for_ty);
|
||||
self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics);
|
||||
let ty = self.table.insert_type_vars(ty);
|
||||
let ty = self.table.normalize_associated_types_in(ty);
|
||||
self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))?
|
||||
|
@ -265,6 +266,9 @@ impl InferenceContext<'_> {
|
|||
resolved_segment,
|
||||
remaining_segments_for_ty,
|
||||
true,
|
||||
&mut |_, _reason| {
|
||||
// FIXME: Report an error.
|
||||
},
|
||||
)
|
||||
});
|
||||
if ty.is_unknown() {
|
||||
|
|
|
@ -666,7 +666,7 @@ impl<'a> InferenceTable<'a> {
|
|||
highest_known_var: InferenceVar,
|
||||
}
|
||||
impl TypeFolder<Interner> for VarFudger<'_, '_> {
|
||||
fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner, Error = Self::Error> {
|
||||
fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner> {
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -794,69 +794,75 @@ impl<'a> InferenceTable<'a> {
|
|||
ty: &Ty,
|
||||
num_args: usize,
|
||||
) -> Option<(FnTrait, Vec<Ty>, Ty)> {
|
||||
let krate = self.trait_env.krate;
|
||||
let fn_once_trait = FnTrait::FnOnce.get_id(self.db, krate)?;
|
||||
let trait_data = self.db.trait_data(fn_once_trait);
|
||||
let output_assoc_type =
|
||||
trait_data.associated_type_by_name(&Name::new_symbol_root(sym::Output.clone()))?;
|
||||
for (fn_trait_name, output_assoc_name, subtraits) in [
|
||||
(FnTrait::FnOnce, sym::Output.clone(), &[FnTrait::Fn, FnTrait::FnMut][..]),
|
||||
(FnTrait::AsyncFnMut, sym::CallRefFuture.clone(), &[FnTrait::AsyncFn]),
|
||||
(FnTrait::AsyncFnOnce, sym::CallOnceFuture.clone(), &[]),
|
||||
] {
|
||||
let krate = self.trait_env.krate;
|
||||
let fn_trait = fn_trait_name.get_id(self.db, krate)?;
|
||||
let trait_data = self.db.trait_data(fn_trait);
|
||||
let output_assoc_type =
|
||||
trait_data.associated_type_by_name(&Name::new_symbol_root(output_assoc_name))?;
|
||||
|
||||
let mut arg_tys = Vec::with_capacity(num_args);
|
||||
let arg_ty = TyBuilder::tuple(num_args)
|
||||
.fill(|it| {
|
||||
let arg = match it {
|
||||
ParamKind::Type => self.new_type_var(),
|
||||
ParamKind::Lifetime => unreachable!("Tuple with lifetime parameter"),
|
||||
ParamKind::Const(_) => unreachable!("Tuple with const parameter"),
|
||||
};
|
||||
arg_tys.push(arg.clone());
|
||||
arg.cast(Interner)
|
||||
})
|
||||
.build();
|
||||
let mut arg_tys = Vec::with_capacity(num_args);
|
||||
let arg_ty = TyBuilder::tuple(num_args)
|
||||
.fill(|it| {
|
||||
let arg = match it {
|
||||
ParamKind::Type => self.new_type_var(),
|
||||
ParamKind::Lifetime => unreachable!("Tuple with lifetime parameter"),
|
||||
ParamKind::Const(_) => unreachable!("Tuple with const parameter"),
|
||||
};
|
||||
arg_tys.push(arg.clone());
|
||||
arg.cast(Interner)
|
||||
})
|
||||
.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();
|
||||
let b = TyBuilder::trait_ref(self.db, fn_trait);
|
||||
if b.remaining() != 2 {
|
||||
return None;
|
||||
}
|
||||
let mut trait_ref = b.push(ty.clone()).push(arg_ty).build();
|
||||
|
||||
let projection = {
|
||||
TyBuilder::assoc_type_projection(
|
||||
let projection = TyBuilder::assoc_type_projection(
|
||||
self.db,
|
||||
output_assoc_type,
|
||||
Some(trait_ref.substitution.clone()),
|
||||
)
|
||||
.build()
|
||||
};
|
||||
.fill_with_unknown()
|
||||
.build();
|
||||
|
||||
let trait_env = self.trait_env.env.clone();
|
||||
let obligation = InEnvironment {
|
||||
goal: trait_ref.clone().cast(Interner),
|
||||
environment: trait_env.clone(),
|
||||
};
|
||||
let canonical = self.canonicalize(obligation.clone());
|
||||
if self.db.trait_solve(krate, self.trait_env.block, canonical.cast(Interner)).is_some() {
|
||||
self.register_obligation(obligation.goal);
|
||||
let return_ty = self.normalize_projection_ty(projection);
|
||||
for fn_x in [FnTrait::Fn, FnTrait::FnMut, FnTrait::FnOnce] {
|
||||
let fn_x_trait = fn_x.get_id(self.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 = self.canonicalize(obligation.clone());
|
||||
if self
|
||||
.db
|
||||
.trait_solve(krate, self.trait_env.block, canonical.cast(Interner))
|
||||
.is_some()
|
||||
{
|
||||
return Some((fn_x, arg_tys, return_ty));
|
||||
let trait_env = self.trait_env.env.clone();
|
||||
let obligation = InEnvironment {
|
||||
goal: trait_ref.clone().cast(Interner),
|
||||
environment: trait_env.clone(),
|
||||
};
|
||||
let canonical = self.canonicalize(obligation.clone());
|
||||
if self.db.trait_solve(krate, self.trait_env.block, canonical.cast(Interner)).is_some()
|
||||
{
|
||||
self.register_obligation(obligation.goal);
|
||||
let return_ty = self.normalize_projection_ty(projection);
|
||||
for &fn_x in subtraits {
|
||||
let fn_x_trait = fn_x.get_id(self.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 = self.canonicalize(obligation.clone());
|
||||
if self
|
||||
.db
|
||||
.trait_solve(krate, self.trait_env.block, canonical.cast(Interner))
|
||||
.is_some()
|
||||
{
|
||||
return Some((fn_x, arg_tys, return_ty));
|
||||
}
|
||||
}
|
||||
return Some((fn_trait_name, arg_tys, return_ty));
|
||||
}
|
||||
unreachable!("It should at least implement FnOnce at this point");
|
||||
} else {
|
||||
None
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn insert_type_vars<T>(&mut self, ty: T) -> T
|
||||
|
@ -1004,7 +1010,7 @@ mod resolve {
|
|||
where
|
||||
F: Fn(InferenceVar, VariableKind, GenericArg, DebruijnIndex) -> GenericArg,
|
||||
{
|
||||
fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner, Error = Self::Error> {
|
||||
fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner> {
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,7 @@ use chalk_ir::{
|
|||
visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor},
|
||||
DebruijnIndex,
|
||||
};
|
||||
use hir_def::{visibility::Visibility, AdtId, EnumVariantId, HasModule, ModuleId, VariantId};
|
||||
use intern::sym;
|
||||
use hir_def::{visibility::Visibility, AdtId, EnumVariantId, ModuleId, VariantId};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::{
|
||||
|
@ -118,11 +117,6 @@ impl UninhabitedFrom<'_> {
|
|||
variant: VariantId,
|
||||
subst: &Substitution,
|
||||
) -> ControlFlow<VisiblyUninhabited> {
|
||||
let is_local = variant.krate(self.db.upcast()) == self.target_mod.krate();
|
||||
if !is_local && self.db.attrs(variant.into()).by_key(&sym::non_exhaustive).exists() {
|
||||
return CONTINUE_OPAQUELY_INHABITED;
|
||||
}
|
||||
|
||||
let variant_data = self.db.variant_data(variant);
|
||||
let fields = variant_data.fields();
|
||||
if fields.is_empty() {
|
||||
|
|
|
@ -84,12 +84,14 @@ pub use infer::{
|
|||
cast::CastError,
|
||||
closure::{CaptureKind, CapturedItem},
|
||||
could_coerce, could_unify, could_unify_deeply, Adjust, Adjustment, AutoBorrow, BindingMode,
|
||||
InferenceDiagnostic, InferenceResult, OverloadedDeref, PointerCast,
|
||||
InferenceDiagnostic, InferenceResult, InferenceTyDiagnosticSource, OverloadedDeref,
|
||||
PointerCast,
|
||||
};
|
||||
pub use interner::Interner;
|
||||
pub use lower::{
|
||||
associated_type_shorthand_candidates, ImplTraitLoweringMode, ParamLoweringMode, TyDefId,
|
||||
TyLoweringContext, ValueTyDefId,
|
||||
associated_type_shorthand_candidates, GenericArgsProhibitedReason, ImplTraitLoweringMode,
|
||||
ParamLoweringMode, TyDefId, TyLoweringContext, TyLoweringDiagnostic, TyLoweringDiagnosticKind,
|
||||
ValueTyDefId,
|
||||
};
|
||||
pub use mapping::{
|
||||
from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, from_placeholder_idx,
|
||||
|
@ -385,7 +387,6 @@ pub enum FnAbi {
|
|||
Fastcall,
|
||||
FastcallUnwind,
|
||||
Msp430Interrupt,
|
||||
PlatformIntrinsic,
|
||||
PtxKernel,
|
||||
RiscvInterruptM,
|
||||
RiscvInterruptS,
|
||||
|
@ -444,7 +445,6 @@ impl FnAbi {
|
|||
s if *s == sym::fastcall_dash_unwind => FnAbi::FastcallUnwind,
|
||||
s if *s == sym::fastcall => FnAbi::Fastcall,
|
||||
s if *s == sym::msp430_dash_interrupt => FnAbi::Msp430Interrupt,
|
||||
s if *s == sym::platform_dash_intrinsic => FnAbi::PlatformIntrinsic,
|
||||
s if *s == sym::ptx_dash_kernel => FnAbi::PtxKernel,
|
||||
s if *s == sym::riscv_dash_interrupt_dash_m => FnAbi::RiscvInterruptM,
|
||||
s if *s == sym::riscv_dash_interrupt_dash_s => FnAbi::RiscvInterruptS,
|
||||
|
@ -487,7 +487,6 @@ impl FnAbi {
|
|||
FnAbi::Fastcall => "fastcall",
|
||||
FnAbi::FastcallUnwind => "fastcall-unwind",
|
||||
FnAbi::Msp430Interrupt => "msp430-interrupt",
|
||||
FnAbi::PlatformIntrinsic => "platform-intrinsic",
|
||||
FnAbi::PtxKernel => "ptx-kernel",
|
||||
FnAbi::RiscvInterruptM => "riscv-interrupt-m",
|
||||
FnAbi::RiscvInterruptS => "riscv-interrupt-s",
|
||||
|
@ -646,7 +645,7 @@ pub(crate) fn fold_free_vars<T: HasInterner<Interner = Interner> + TypeFoldable<
|
|||
F2: FnMut(Ty, BoundVar, DebruijnIndex) -> Const,
|
||||
> TypeFolder<Interner> for FreeVarFolder<F1, F2>
|
||||
{
|
||||
fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner, Error = Self::Error> {
|
||||
fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner> {
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -697,7 +696,7 @@ pub(crate) fn fold_tys_and_consts<T: HasInterner<Interner = Interner> + TypeFold
|
|||
impl<F: FnMut(Either<Ty, Const>, DebruijnIndex) -> Either<Ty, Const>> TypeFolder<Interner>
|
||||
for TyFolder<F>
|
||||
{
|
||||
fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner, Error = Self::Error> {
|
||||
fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner> {
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
@ -33,8 +33,8 @@ use hir_def::{
|
|||
path::{GenericArg, GenericArgs, ModPath, Path, PathKind, PathSegment, PathSegments},
|
||||
resolver::{HasResolver, LifetimeNs, Resolver, TypeNs},
|
||||
type_ref::{
|
||||
ConstRef, LifetimeRef, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, TypeRef,
|
||||
TypeRefId, TypesMap, TypesSourceMap,
|
||||
ConstRef, LifetimeRef, PathId, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound,
|
||||
TypeRef, TypeRefId, TypesMap, TypesSourceMap,
|
||||
},
|
||||
AdtId, AssocItemId, CallableDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId,
|
||||
FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, InTypeConstLoc, ItemContainerId,
|
||||
|
@ -48,7 +48,7 @@ use rustc_pattern_analysis::Captures;
|
|||
use smallvec::SmallVec;
|
||||
use stdx::{impl_from, never};
|
||||
use syntax::ast;
|
||||
use triomphe::Arc;
|
||||
use triomphe::{Arc, ThinArc};
|
||||
|
||||
use crate::{
|
||||
all_super_traits,
|
||||
|
@ -102,6 +102,31 @@ impl ImplTraitLoweringState {
|
|||
}
|
||||
}
|
||||
|
||||
type TypeSource = Either<TypeRefId, hir_def::type_ref::TypeSource>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct TyLoweringDiagnostic {
|
||||
pub source: TypeSource,
|
||||
pub kind: TyLoweringDiagnosticKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum TyLoweringDiagnosticKind {
|
||||
GenericArgsProhibited { segment: u32, reason: GenericArgsProhibitedReason },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum GenericArgsProhibitedReason {
|
||||
Module,
|
||||
TyParam,
|
||||
SelfTy,
|
||||
PrimitiveTy,
|
||||
/// When there is a generic enum, within the expression `Enum::Variant`,
|
||||
/// either `Enum` or `Variant` are allowed to have generic arguments, but not both.
|
||||
// FIXME: This is not used now but it should be.
|
||||
EnumVariant,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TyLoweringContext<'a> {
|
||||
pub db: &'a dyn HirDatabase,
|
||||
|
@ -125,6 +150,7 @@ pub struct TyLoweringContext<'a> {
|
|||
expander: Option<Expander>,
|
||||
/// Tracks types with explicit `?Sized` bounds.
|
||||
pub(crate) unsized_types: FxHashSet<Ty>,
|
||||
pub(crate) diagnostics: Vec<TyLoweringDiagnostic>,
|
||||
}
|
||||
|
||||
impl<'a> TyLoweringContext<'a> {
|
||||
|
@ -159,6 +185,7 @@ impl<'a> TyLoweringContext<'a> {
|
|||
type_param_mode,
|
||||
expander: None,
|
||||
unsized_types: FxHashSet::default(),
|
||||
diagnostics: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,6 +225,20 @@ impl<'a> TyLoweringContext<'a> {
|
|||
self.type_param_mode = type_param_mode;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn push_diagnostic(&mut self, type_ref: TypeRefId, kind: TyLoweringDiagnosticKind) {
|
||||
let source = match self.types_source_map {
|
||||
Some(source_map) => {
|
||||
let Ok(source) = source_map.type_syntax(type_ref) else {
|
||||
stdx::never!("error in synthetic type");
|
||||
return;
|
||||
};
|
||||
Either::Right(source)
|
||||
}
|
||||
None => Either::Left(type_ref),
|
||||
};
|
||||
self.diagnostics.push(TyLoweringDiagnostic { source, kind });
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
|
||||
|
@ -264,7 +305,8 @@ impl<'a> TyLoweringContext<'a> {
|
|||
.intern(Interner)
|
||||
}
|
||||
TypeRef::Path(path) => {
|
||||
let (ty, res_) = self.lower_path(path);
|
||||
let (ty, res_) =
|
||||
self.lower_path(path, PathId::from_type_ref_unchecked(type_ref_id));
|
||||
res = res_;
|
||||
ty
|
||||
}
|
||||
|
@ -463,6 +505,7 @@ impl<'a> TyLoweringContext<'a> {
|
|||
impl_trait_mode: mem::take(&mut self.impl_trait_mode),
|
||||
expander: self.expander.take(),
|
||||
unsized_types: mem::take(&mut self.unsized_types),
|
||||
diagnostics: mem::take(&mut self.diagnostics),
|
||||
};
|
||||
|
||||
let ty = inner_ctx.lower_ty(type_ref);
|
||||
|
@ -470,6 +513,7 @@ impl<'a> TyLoweringContext<'a> {
|
|||
self.impl_trait_mode = inner_ctx.impl_trait_mode;
|
||||
self.expander = inner_ctx.expander;
|
||||
self.unsized_types = inner_ctx.unsized_types;
|
||||
self.diagnostics = inner_ctx.diagnostics;
|
||||
|
||||
self.expander.as_mut().unwrap().exit(mark);
|
||||
Some(ty)
|
||||
|
@ -541,6 +585,10 @@ impl<'a> TyLoweringContext<'a> {
|
|||
resolved_segment: PathSegment<'_>,
|
||||
remaining_segments: PathSegments<'_>,
|
||||
infer_args: bool,
|
||||
on_prohibited_generics_for_resolved_segment: &mut dyn FnMut(
|
||||
&mut Self,
|
||||
GenericArgsProhibitedReason,
|
||||
),
|
||||
) -> (Ty, Option<TypeNs>) {
|
||||
let ty = match resolution {
|
||||
TypeNs::TraitId(trait_) => {
|
||||
|
@ -607,28 +655,44 @@ impl<'a> TyLoweringContext<'a> {
|
|||
// FIXME(trait_alias): Implement trait alias.
|
||||
return (TyKind::Error.intern(Interner), None);
|
||||
}
|
||||
TypeNs::GenericParam(param_id) => match self.type_param_mode {
|
||||
ParamLoweringMode::Placeholder => {
|
||||
TyKind::Placeholder(to_placeholder_idx(self.db, param_id.into()))
|
||||
TypeNs::GenericParam(param_id) => {
|
||||
if resolved_segment.args_and_bindings.is_some() {
|
||||
on_prohibited_generics_for_resolved_segment(
|
||||
self,
|
||||
GenericArgsProhibitedReason::TyParam,
|
||||
);
|
||||
}
|
||||
ParamLoweringMode::Variable => {
|
||||
let idx = match self
|
||||
.generics()
|
||||
.expect("generics in scope")
|
||||
.type_or_const_param_idx(param_id.into())
|
||||
{
|
||||
None => {
|
||||
never!("no matching generics");
|
||||
return (TyKind::Error.intern(Interner), None);
|
||||
}
|
||||
Some(idx) => idx,
|
||||
};
|
||||
|
||||
TyKind::BoundVar(BoundVar::new(self.in_binders, idx))
|
||||
match self.type_param_mode {
|
||||
ParamLoweringMode::Placeholder => {
|
||||
TyKind::Placeholder(to_placeholder_idx(self.db, param_id.into()))
|
||||
}
|
||||
ParamLoweringMode::Variable => {
|
||||
let idx = match self
|
||||
.generics()
|
||||
.expect("generics in scope")
|
||||
.type_or_const_param_idx(param_id.into())
|
||||
{
|
||||
None => {
|
||||
never!("no matching generics");
|
||||
return (TyKind::Error.intern(Interner), None);
|
||||
}
|
||||
Some(idx) => idx,
|
||||
};
|
||||
|
||||
TyKind::BoundVar(BoundVar::new(self.in_binders, idx))
|
||||
}
|
||||
}
|
||||
.intern(Interner)
|
||||
}
|
||||
.intern(Interner),
|
||||
TypeNs::SelfType(impl_id) => {
|
||||
if resolved_segment.args_and_bindings.is_some() {
|
||||
on_prohibited_generics_for_resolved_segment(
|
||||
self,
|
||||
GenericArgsProhibitedReason::SelfTy,
|
||||
);
|
||||
}
|
||||
|
||||
let generics = self.generics().expect("impl should have generic param scope");
|
||||
|
||||
match self.type_param_mode {
|
||||
|
@ -654,6 +718,13 @@ impl<'a> TyLoweringContext<'a> {
|
|||
}
|
||||
}
|
||||
TypeNs::AdtSelfType(adt) => {
|
||||
if resolved_segment.args_and_bindings.is_some() {
|
||||
on_prohibited_generics_for_resolved_segment(
|
||||
self,
|
||||
GenericArgsProhibitedReason::SelfTy,
|
||||
);
|
||||
}
|
||||
|
||||
let generics = generics(self.db.upcast(), adt.into());
|
||||
let substs = match self.type_param_mode {
|
||||
ParamLoweringMode::Placeholder => generics.placeholder_subst(self.db),
|
||||
|
@ -666,6 +737,12 @@ impl<'a> TyLoweringContext<'a> {
|
|||
|
||||
TypeNs::AdtId(it) => self.lower_path_inner(resolved_segment, it.into(), infer_args),
|
||||
TypeNs::BuiltinType(it) => {
|
||||
if resolved_segment.args_and_bindings.is_some() {
|
||||
on_prohibited_generics_for_resolved_segment(
|
||||
self,
|
||||
GenericArgsProhibitedReason::PrimitiveTy,
|
||||
);
|
||||
}
|
||||
self.lower_path_inner(resolved_segment, it.into(), infer_args)
|
||||
}
|
||||
TypeNs::TypeAliasId(it) => {
|
||||
|
@ -677,7 +754,7 @@ impl<'a> TyLoweringContext<'a> {
|
|||
self.lower_ty_relative_path(ty, Some(resolution), remaining_segments)
|
||||
}
|
||||
|
||||
pub(crate) fn lower_path(&mut self, path: &Path) -> (Ty, Option<TypeNs>) {
|
||||
pub(crate) fn lower_path(&mut self, path: &Path, path_id: PathId) -> (Ty, Option<TypeNs>) {
|
||||
// Resolve the path (in type namespace)
|
||||
if let Some(type_ref) = path.type_anchor() {
|
||||
let (ty, res) = self.lower_ty_ext(type_ref);
|
||||
|
@ -692,19 +769,44 @@ impl<'a> TyLoweringContext<'a> {
|
|||
|
||||
if matches!(resolution, TypeNs::TraitId(_)) && remaining_index.is_none() {
|
||||
// trait object type without dyn
|
||||
let bound = TypeBound::Path(path.clone(), TraitBoundModifier::None);
|
||||
let bound = TypeBound::Path(path_id, TraitBoundModifier::None);
|
||||
let ty = self.lower_dyn_trait(&[bound]);
|
||||
return (ty, None);
|
||||
}
|
||||
|
||||
let (resolved_segment, remaining_segments) = match remaining_index {
|
||||
None => (
|
||||
path.segments().last().expect("resolved path has at least one element"),
|
||||
PathSegments::EMPTY,
|
||||
),
|
||||
Some(i) => (path.segments().get(i - 1).unwrap(), path.segments().skip(i)),
|
||||
};
|
||||
self.lower_partly_resolved_path(resolution, resolved_segment, remaining_segments, false)
|
||||
let (module_segments, resolved_segment_idx, resolved_segment, remaining_segments) =
|
||||
match remaining_index {
|
||||
None => (
|
||||
path.segments().strip_last(),
|
||||
path.segments().len() - 1,
|
||||
path.segments().last().expect("resolved path has at least one element"),
|
||||
PathSegments::EMPTY,
|
||||
),
|
||||
Some(i) => (
|
||||
path.segments().take(i - 1),
|
||||
i - 1,
|
||||
path.segments().get(i - 1).unwrap(),
|
||||
path.segments().skip(i),
|
||||
),
|
||||
};
|
||||
|
||||
self.prohibit_generics(path_id, 0, module_segments, GenericArgsProhibitedReason::Module);
|
||||
|
||||
self.lower_partly_resolved_path(
|
||||
resolution,
|
||||
resolved_segment,
|
||||
remaining_segments,
|
||||
false,
|
||||
&mut |this, reason| {
|
||||
this.push_diagnostic(
|
||||
path_id.type_ref(),
|
||||
TyLoweringDiagnosticKind::GenericArgsProhibited {
|
||||
segment: resolved_segment_idx as u32,
|
||||
reason,
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn select_associated_type(&mut self, res: Option<TypeNs>, segment: PathSegment<'_>) -> Ty {
|
||||
|
@ -741,12 +843,8 @@ impl<'a> TyLoweringContext<'a> {
|
|||
// generic params. It's inefficient to splice the `Substitution`s, so we may want
|
||||
// that method to optionally take parent `Substitution` as we already know them at
|
||||
// this point (`t.substitution`).
|
||||
let substs = self.substs_from_path_segment(
|
||||
segment.clone(),
|
||||
Some(associated_ty.into()),
|
||||
false,
|
||||
None,
|
||||
);
|
||||
let substs =
|
||||
self.substs_from_path_segment(segment, Some(associated_ty.into()), false, None);
|
||||
|
||||
let len_self =
|
||||
crate::generics::generics(self.db.upcast(), associated_ty.into()).len_self();
|
||||
|
@ -998,12 +1096,41 @@ impl<'a> TyLoweringContext<'a> {
|
|||
TraitRef { trait_id: to_chalk_trait_id(resolved), substitution: substs }
|
||||
}
|
||||
|
||||
fn lower_trait_ref_from_path(&mut self, path: &Path, explicit_self_ty: Ty) -> Option<TraitRef> {
|
||||
fn prohibit_generics(
|
||||
&mut self,
|
||||
path_id: PathId,
|
||||
idx: u32,
|
||||
segments: PathSegments<'_>,
|
||||
reason: GenericArgsProhibitedReason,
|
||||
) {
|
||||
segments.iter().zip(idx..).for_each(|(segment, idx)| {
|
||||
if segment.args_and_bindings.is_some() {
|
||||
self.push_diagnostic(
|
||||
path_id.type_ref(),
|
||||
TyLoweringDiagnosticKind::GenericArgsProhibited { segment: idx, reason },
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn lower_trait_ref_from_path(
|
||||
&mut self,
|
||||
path_id: PathId,
|
||||
explicit_self_ty: Ty,
|
||||
) -> Option<TraitRef> {
|
||||
let path = &self.types_map[path_id];
|
||||
let resolved = match self.resolver.resolve_path_in_type_ns_fully(self.db.upcast(), path)? {
|
||||
// FIXME(trait_alias): We need to handle trait alias here.
|
||||
TypeNs::TraitId(tr) => tr,
|
||||
_ => return None,
|
||||
};
|
||||
// Do this after we verify it's indeed a trait to not confuse the user if they're not modules.
|
||||
self.prohibit_generics(
|
||||
path_id,
|
||||
0,
|
||||
path.segments().strip_last(),
|
||||
GenericArgsProhibitedReason::Module,
|
||||
);
|
||||
let segment = path.segments().last().expect("path should have at least one segment");
|
||||
Some(self.lower_trait_ref_from_resolved_path(resolved, segment, explicit_self_ty))
|
||||
}
|
||||
|
@ -1013,7 +1140,7 @@ impl<'a> TyLoweringContext<'a> {
|
|||
trait_ref: &HirTraitRef,
|
||||
explicit_self_ty: Ty,
|
||||
) -> Option<TraitRef> {
|
||||
self.lower_trait_ref_from_path(&trait_ref.path, explicit_self_ty)
|
||||
self.lower_trait_ref_from_path(trait_ref.path, explicit_self_ty)
|
||||
}
|
||||
|
||||
fn trait_ref_substs_from_path(
|
||||
|
@ -1072,11 +1199,11 @@ impl<'a> TyLoweringContext<'a> {
|
|||
) -> impl Iterator<Item = QuantifiedWhereClause> + use<'b, 'a> {
|
||||
let mut trait_ref = None;
|
||||
let clause = match bound {
|
||||
TypeBound::Path(path, TraitBoundModifier::None) => {
|
||||
&TypeBound::Path(path, TraitBoundModifier::None) => {
|
||||
trait_ref = self.lower_trait_ref_from_path(path, self_ty);
|
||||
trait_ref.clone().map(WhereClause::Implemented).map(crate::wrap_empty_binders)
|
||||
}
|
||||
TypeBound::Path(path, TraitBoundModifier::Maybe) => {
|
||||
&TypeBound::Path(path, TraitBoundModifier::Maybe) => {
|
||||
let sized_trait = self
|
||||
.db
|
||||
.lang_item(self.resolver.krate(), LangItem::Sized)
|
||||
|
@ -1092,7 +1219,7 @@ impl<'a> TyLoweringContext<'a> {
|
|||
}
|
||||
None
|
||||
}
|
||||
TypeBound::ForLifetime(_, path) => {
|
||||
&TypeBound::ForLifetime(_, path) => {
|
||||
// FIXME Don't silently drop the hrtb lifetimes here
|
||||
trait_ref = self.lower_trait_ref_from_path(path, self_ty);
|
||||
trait_ref.clone().map(WhereClause::Implemented).map(crate::wrap_empty_binders)
|
||||
|
@ -1121,8 +1248,8 @@ impl<'a> TyLoweringContext<'a> {
|
|||
trait_ref: TraitRef,
|
||||
) -> impl Iterator<Item = QuantifiedWhereClause> + use<'b, 'a> {
|
||||
let last_segment = match bound {
|
||||
TypeBound::Path(path, TraitBoundModifier::None) | TypeBound::ForLifetime(_, path) => {
|
||||
path.segments().last()
|
||||
&TypeBound::Path(path, TraitBoundModifier::None) | &TypeBound::ForLifetime(_, path) => {
|
||||
self.types_map[path].segments().last()
|
||||
}
|
||||
TypeBound::Path(_, TraitBoundModifier::Maybe)
|
||||
| TypeBound::Use(_)
|
||||
|
@ -1227,7 +1354,9 @@ impl<'a> TyLoweringContext<'a> {
|
|||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
ext.lower_ty(type_ref)
|
||||
let ty = ext.lower_ty(type_ref);
|
||||
self.diagnostics.extend(ext.diagnostics);
|
||||
ty
|
||||
} else {
|
||||
self.lower_ty(type_ref)
|
||||
};
|
||||
|
@ -1523,11 +1652,24 @@ fn named_associated_type_shorthand_candidates<R>(
|
|||
}
|
||||
}
|
||||
|
||||
/// Build the type of all specific fields of a struct or enum variant.
|
||||
pub(crate) type Diagnostics = Option<ThinArc<(), TyLoweringDiagnostic>>;
|
||||
|
||||
fn create_diagnostics(diagnostics: Vec<TyLoweringDiagnostic>) -> Diagnostics {
|
||||
(!diagnostics.is_empty()).then(|| ThinArc::from_header_and_iter((), diagnostics.into_iter()))
|
||||
}
|
||||
|
||||
pub(crate) fn field_types_query(
|
||||
db: &dyn HirDatabase,
|
||||
variant_id: VariantId,
|
||||
) -> Arc<ArenaMap<LocalFieldId, Binders<Ty>>> {
|
||||
db.field_types_with_diagnostics(variant_id).0
|
||||
}
|
||||
|
||||
/// Build the type of all specific fields of a struct or enum variant.
|
||||
pub(crate) fn field_types_with_diagnostics_query(
|
||||
db: &dyn HirDatabase,
|
||||
variant_id: VariantId,
|
||||
) -> (Arc<ArenaMap<LocalFieldId, Binders<Ty>>>, Diagnostics) {
|
||||
let var_data = variant_id.variant_data(db.upcast());
|
||||
let (resolver, def): (_, GenericDefId) = match variant_id {
|
||||
VariantId::StructId(it) => (it.resolver(db.upcast()), it.into()),
|
||||
|
@ -1543,7 +1685,7 @@ pub(crate) fn field_types_query(
|
|||
for (field_id, field_data) in var_data.fields().iter() {
|
||||
res.insert(field_id, make_binders(db, &generics, ctx.lower_ty(field_data.type_ref)));
|
||||
}
|
||||
Arc::new(res)
|
||||
(Arc::new(res), create_diagnostics(ctx.diagnostics))
|
||||
}
|
||||
|
||||
/// This query exists only to be used when resolving short-hand associated types
|
||||
|
@ -1593,9 +1735,10 @@ pub(crate) fn generic_predicates_for_param_query(
|
|||
}
|
||||
|
||||
match bound {
|
||||
TypeBound::ForLifetime(_, path) | TypeBound::Path(path, _) => {
|
||||
&TypeBound::ForLifetime(_, path) | &TypeBound::Path(path, _) => {
|
||||
// Only lower the bound if the trait could possibly define the associated
|
||||
// type we're looking for.
|
||||
let path = &ctx.types_map[path];
|
||||
|
||||
let Some(assoc_name) = &assoc_name else { return true };
|
||||
let Some(TypeNs::TraitId(tr)) =
|
||||
|
@ -1743,15 +1886,22 @@ pub(crate) fn generic_predicates_query(
|
|||
db: &dyn HirDatabase,
|
||||
def: GenericDefId,
|
||||
) -> GenericPredicates {
|
||||
generic_predicates_filtered_by(db, def, |_, _| true)
|
||||
generic_predicates_filtered_by(db, def, |_, _| true).0
|
||||
}
|
||||
|
||||
/// Resolve the where clause(s) of an item with generics,
|
||||
/// except the ones inherited from the parent
|
||||
pub(crate) fn generic_predicates_without_parent_query(
|
||||
db: &dyn HirDatabase,
|
||||
def: GenericDefId,
|
||||
) -> GenericPredicates {
|
||||
db.generic_predicates_without_parent_with_diagnostics(def).0
|
||||
}
|
||||
|
||||
/// Resolve the where clause(s) of an item with generics,
|
||||
/// except the ones inherited from the parent
|
||||
pub(crate) fn generic_predicates_without_parent_with_diagnostics_query(
|
||||
db: &dyn HirDatabase,
|
||||
def: GenericDefId,
|
||||
) -> (GenericPredicates, Diagnostics) {
|
||||
generic_predicates_filtered_by(db, def, |_, d| *d == def)
|
||||
}
|
||||
|
||||
|
@ -1761,7 +1911,7 @@ fn generic_predicates_filtered_by<F>(
|
|||
db: &dyn HirDatabase,
|
||||
def: GenericDefId,
|
||||
filter: F,
|
||||
) -> GenericPredicates
|
||||
) -> (GenericPredicates, Diagnostics)
|
||||
where
|
||||
F: Fn(&WherePredicate, &GenericDefId) -> bool,
|
||||
{
|
||||
|
@ -1802,7 +1952,10 @@ where
|
|||
);
|
||||
};
|
||||
}
|
||||
GenericPredicates(predicates.is_empty().not().then(|| predicates.into()))
|
||||
(
|
||||
GenericPredicates(predicates.is_empty().not().then(|| predicates.into())),
|
||||
create_diagnostics(ctx.diagnostics),
|
||||
)
|
||||
}
|
||||
|
||||
/// Generate implicit `: Sized` predicates for all generics that has no `?Sized` bound.
|
||||
|
@ -1855,75 +2008,110 @@ impl ops::Deref for GenericDefaults {
|
|||
}
|
||||
}
|
||||
|
||||
/// Resolve the default type params from generics
|
||||
pub(crate) fn generic_defaults_query(db: &dyn HirDatabase, def: GenericDefId) -> GenericDefaults {
|
||||
db.generic_defaults_with_diagnostics(def).0
|
||||
}
|
||||
|
||||
/// Resolve the default type params from generics.
|
||||
///
|
||||
/// Diagnostics are only returned for this `GenericDefId` (returned defaults include parents).
|
||||
pub(crate) fn generic_defaults_with_diagnostics_query(
|
||||
db: &dyn HirDatabase,
|
||||
def: GenericDefId,
|
||||
) -> (GenericDefaults, Diagnostics) {
|
||||
let generic_params = generics(db.upcast(), def);
|
||||
if generic_params.len() == 0 {
|
||||
return GenericDefaults(None);
|
||||
return (GenericDefaults(None), None);
|
||||
}
|
||||
let resolver = def.resolver(db.upcast());
|
||||
let parent_start_idx = generic_params.len_self();
|
||||
|
||||
let mut ctx = TyLoweringContext::new(db, &resolver, TypesMap::EMPTY, def.into())
|
||||
.with_impl_trait_mode(ImplTraitLoweringMode::Disallowed)
|
||||
.with_type_param_mode(ParamLoweringMode::Variable);
|
||||
GenericDefaults(Some(Arc::from_iter(generic_params.iter_with_types_map().enumerate().map(
|
||||
|(idx, ((id, p), types_map))| {
|
||||
ctx.types_map = types_map;
|
||||
match p {
|
||||
GenericParamDataRef::TypeParamData(p) => {
|
||||
let ty = p.default.as_ref().map_or(TyKind::Error.intern(Interner), |ty| {
|
||||
// Each default can only refer to previous parameters.
|
||||
// Type variable default referring to parameter coming
|
||||
// after it is forbidden (FIXME: report diagnostic)
|
||||
fallback_bound_vars(ctx.lower_ty(*ty), idx, parent_start_idx)
|
||||
});
|
||||
crate::make_binders(db, &generic_params, ty.cast(Interner))
|
||||
}
|
||||
GenericParamDataRef::ConstParamData(p) => {
|
||||
let GenericParamId::ConstParamId(id) = id else {
|
||||
unreachable!("Unexpected lifetime or type argument")
|
||||
};
|
||||
let mut ctx =
|
||||
TyLoweringContext::new(db, &resolver, generic_params.self_types_map(), def.into())
|
||||
.with_impl_trait_mode(ImplTraitLoweringMode::Disallowed)
|
||||
.with_type_param_mode(ParamLoweringMode::Variable);
|
||||
let mut idx = 0;
|
||||
let mut defaults = generic_params
|
||||
.iter_self()
|
||||
.map(|(id, p)| {
|
||||
let result =
|
||||
handle_generic_param(&mut ctx, idx, id, p, parent_start_idx, &generic_params);
|
||||
idx += 1;
|
||||
result
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let diagnostics = create_diagnostics(mem::take(&mut ctx.diagnostics));
|
||||
defaults.extend(generic_params.iter_parents_with_types_map().map(|((id, p), types_map)| {
|
||||
ctx.types_map = types_map;
|
||||
let result = handle_generic_param(&mut ctx, idx, id, p, parent_start_idx, &generic_params);
|
||||
idx += 1;
|
||||
result
|
||||
}));
|
||||
let defaults = GenericDefaults(Some(Arc::from_iter(defaults)));
|
||||
return (defaults, diagnostics);
|
||||
|
||||
let mut val = p.default.as_ref().map_or_else(
|
||||
|| unknown_const_as_generic(db.const_param_ty(id)),
|
||||
|c| {
|
||||
let param_ty = ctx.lower_ty(p.ty);
|
||||
let c = ctx.lower_const(c, param_ty);
|
||||
c.cast(Interner)
|
||||
},
|
||||
);
|
||||
// Each default can only refer to previous parameters, see above.
|
||||
val = fallback_bound_vars(val, idx, parent_start_idx);
|
||||
make_binders(db, &generic_params, val)
|
||||
}
|
||||
GenericParamDataRef::LifetimeParamData(_) => {
|
||||
make_binders(db, &generic_params, error_lifetime().cast(Interner))
|
||||
}
|
||||
fn handle_generic_param(
|
||||
ctx: &mut TyLoweringContext<'_>,
|
||||
idx: usize,
|
||||
id: GenericParamId,
|
||||
p: GenericParamDataRef<'_>,
|
||||
parent_start_idx: usize,
|
||||
generic_params: &Generics,
|
||||
) -> Binders<crate::GenericArg> {
|
||||
match p {
|
||||
GenericParamDataRef::TypeParamData(p) => {
|
||||
let ty = p.default.as_ref().map_or(TyKind::Error.intern(Interner), |ty| {
|
||||
// Each default can only refer to previous parameters.
|
||||
// Type variable default referring to parameter coming
|
||||
// after it is forbidden (FIXME: report diagnostic)
|
||||
fallback_bound_vars(ctx.lower_ty(*ty), idx, parent_start_idx)
|
||||
});
|
||||
crate::make_binders(ctx.db, generic_params, ty.cast(Interner))
|
||||
}
|
||||
},
|
||||
))))
|
||||
GenericParamDataRef::ConstParamData(p) => {
|
||||
let GenericParamId::ConstParamId(id) = id else {
|
||||
unreachable!("Unexpected lifetime or type argument")
|
||||
};
|
||||
|
||||
let mut val = p.default.as_ref().map_or_else(
|
||||
|| unknown_const_as_generic(ctx.db.const_param_ty(id)),
|
||||
|c| {
|
||||
let param_ty = ctx.lower_ty(p.ty);
|
||||
let c = ctx.lower_const(c, param_ty);
|
||||
c.cast(Interner)
|
||||
},
|
||||
);
|
||||
// Each default can only refer to previous parameters, see above.
|
||||
val = fallback_bound_vars(val, idx, parent_start_idx);
|
||||
make_binders(ctx.db, generic_params, val)
|
||||
}
|
||||
GenericParamDataRef::LifetimeParamData(_) => {
|
||||
make_binders(ctx.db, generic_params, error_lifetime().cast(Interner))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generic_defaults_recover(
|
||||
pub(crate) fn generic_defaults_with_diagnostics_recover(
|
||||
db: &dyn HirDatabase,
|
||||
_cycle: &Cycle,
|
||||
def: &GenericDefId,
|
||||
) -> GenericDefaults {
|
||||
) -> (GenericDefaults, Diagnostics) {
|
||||
let generic_params = generics(db.upcast(), *def);
|
||||
if generic_params.len() == 0 {
|
||||
return GenericDefaults(None);
|
||||
return (GenericDefaults(None), None);
|
||||
}
|
||||
// FIXME: this code is not covered in tests.
|
||||
// we still need one default per parameter
|
||||
GenericDefaults(Some(Arc::from_iter(generic_params.iter_id().map(|id| {
|
||||
let defaults = GenericDefaults(Some(Arc::from_iter(generic_params.iter_id().map(|id| {
|
||||
let val = match id {
|
||||
GenericParamId::TypeParamId(_) => TyKind::Error.intern(Interner).cast(Interner),
|
||||
GenericParamId::ConstParamId(id) => unknown_const_as_generic(db.const_param_ty(id)),
|
||||
GenericParamId::LifetimeParamId(_) => error_lifetime().cast(Interner),
|
||||
};
|
||||
crate::make_binders(db, &generic_params, val)
|
||||
}))))
|
||||
}))));
|
||||
(defaults, None)
|
||||
}
|
||||
|
||||
fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig {
|
||||
|
@ -2066,7 +2254,10 @@ fn type_for_adt(db: &dyn HirDatabase, adt: AdtId) -> Binders<Ty> {
|
|||
make_binders(db, &generics, ty)
|
||||
}
|
||||
|
||||
fn type_for_type_alias(db: &dyn HirDatabase, t: TypeAliasId) -> Binders<Ty> {
|
||||
pub(crate) fn type_for_type_alias_with_diagnostics_query(
|
||||
db: &dyn HirDatabase,
|
||||
t: TypeAliasId,
|
||||
) -> (Binders<Ty>, Diagnostics) {
|
||||
let generics = generics(db.upcast(), t.into());
|
||||
let resolver = t.resolver(db.upcast());
|
||||
let type_alias_data = db.type_alias_data(t);
|
||||
|
@ -2081,7 +2272,7 @@ fn type_for_type_alias(db: &dyn HirDatabase, t: TypeAliasId) -> Binders<Ty> {
|
|||
.map(|type_ref| ctx.lower_ty(type_ref))
|
||||
.unwrap_or_else(|| TyKind::Error.intern(Interner))
|
||||
};
|
||||
make_binders(db, &generics, inner)
|
||||
(make_binders(db, &generics, inner), create_diagnostics(ctx.diagnostics))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
@ -2124,7 +2315,7 @@ pub(crate) fn ty_query(db: &dyn HirDatabase, def: TyDefId) -> Binders<Ty> {
|
|||
match def {
|
||||
TyDefId::BuiltinType(it) => Binders::empty(Interner, TyBuilder::builtin(it)),
|
||||
TyDefId::AdtId(it) => type_for_adt(db, it),
|
||||
TyDefId::TypeAliasId(it) => type_for_type_alias(db, it),
|
||||
TyDefId::TypeAliasId(it) => db.type_for_type_alias_with_diagnostics(it).0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2149,47 +2340,73 @@ pub(crate) fn value_ty_query(db: &dyn HirDatabase, def: ValueTyDefId) -> Option<
|
|||
}
|
||||
|
||||
pub(crate) fn impl_self_ty_query(db: &dyn HirDatabase, impl_id: ImplId) -> Binders<Ty> {
|
||||
db.impl_self_ty_with_diagnostics(impl_id).0
|
||||
}
|
||||
|
||||
pub(crate) fn impl_self_ty_with_diagnostics_query(
|
||||
db: &dyn HirDatabase,
|
||||
impl_id: ImplId,
|
||||
) -> (Binders<Ty>, Diagnostics) {
|
||||
let impl_data = db.impl_data(impl_id);
|
||||
let resolver = impl_id.resolver(db.upcast());
|
||||
let generics = generics(db.upcast(), impl_id.into());
|
||||
let mut ctx = TyLoweringContext::new(db, &resolver, &impl_data.types_map, impl_id.into())
|
||||
.with_type_param_mode(ParamLoweringMode::Variable);
|
||||
make_binders(db, &generics, ctx.lower_ty(impl_data.self_ty))
|
||||
(
|
||||
make_binders(db, &generics, ctx.lower_ty(impl_data.self_ty)),
|
||||
create_diagnostics(ctx.diagnostics),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn const_param_ty_query(db: &dyn HirDatabase, def: ConstParamId) -> Ty {
|
||||
db.const_param_ty_with_diagnostics(def).0
|
||||
}
|
||||
|
||||
// returns None if def is a type arg
|
||||
pub(crate) fn const_param_ty_query(db: &dyn HirDatabase, def: ConstParamId) -> Ty {
|
||||
pub(crate) fn const_param_ty_with_diagnostics_query(
|
||||
db: &dyn HirDatabase,
|
||||
def: ConstParamId,
|
||||
) -> (Ty, Diagnostics) {
|
||||
let parent_data = db.generic_params(def.parent());
|
||||
let data = &parent_data[def.local_id()];
|
||||
let resolver = def.parent().resolver(db.upcast());
|
||||
let mut ctx =
|
||||
TyLoweringContext::new(db, &resolver, &parent_data.types_map, def.parent().into());
|
||||
match data {
|
||||
let ty = match data {
|
||||
TypeOrConstParamData::TypeParamData(_) => {
|
||||
never!();
|
||||
Ty::new(Interner, TyKind::Error)
|
||||
}
|
||||
TypeOrConstParamData::ConstParamData(d) => ctx.lower_ty(d.ty),
|
||||
}
|
||||
};
|
||||
(ty, create_diagnostics(ctx.diagnostics))
|
||||
}
|
||||
|
||||
pub(crate) fn impl_self_ty_recover(
|
||||
pub(crate) fn impl_self_ty_with_diagnostics_recover(
|
||||
db: &dyn HirDatabase,
|
||||
_cycle: &Cycle,
|
||||
impl_id: &ImplId,
|
||||
) -> Binders<Ty> {
|
||||
) -> (Binders<Ty>, Diagnostics) {
|
||||
let generics = generics(db.upcast(), (*impl_id).into());
|
||||
make_binders(db, &generics, TyKind::Error.intern(Interner))
|
||||
(make_binders(db, &generics, TyKind::Error.intern(Interner)), None)
|
||||
}
|
||||
|
||||
pub(crate) fn impl_trait_query(db: &dyn HirDatabase, impl_id: ImplId) -> Option<Binders<TraitRef>> {
|
||||
db.impl_trait_with_diagnostics(impl_id).map(|it| it.0)
|
||||
}
|
||||
|
||||
pub(crate) fn impl_trait_with_diagnostics_query(
|
||||
db: &dyn HirDatabase,
|
||||
impl_id: ImplId,
|
||||
) -> Option<(Binders<TraitRef>, Diagnostics)> {
|
||||
let impl_data = db.impl_data(impl_id);
|
||||
let resolver = impl_id.resolver(db.upcast());
|
||||
let mut ctx = TyLoweringContext::new(db, &resolver, &impl_data.types_map, impl_id.into())
|
||||
.with_type_param_mode(ParamLoweringMode::Variable);
|
||||
let (self_ty, binders) = db.impl_self_ty(impl_id).into_value_and_skipped_binders();
|
||||
let target_trait = impl_data.target_trait.as_ref()?;
|
||||
Some(Binders::new(binders, ctx.lower_trait_ref(target_trait, self_ty)?))
|
||||
let trait_ref = Binders::new(binders, ctx.lower_trait_ref(target_trait, self_ty)?);
|
||||
Some((trait_ref, create_diagnostics(ctx.diagnostics)))
|
||||
}
|
||||
|
||||
pub(crate) fn return_type_impl_traits(
|
||||
|
|
|
@ -2023,11 +2023,11 @@ pub fn mir_body_for_closure_query(
|
|||
ctx.result.locals.alloc(Local { ty: infer[*root].clone() });
|
||||
let closure_local = ctx.result.locals.alloc(Local {
|
||||
ty: match kind {
|
||||
FnTrait::FnOnce => infer[expr].clone(),
|
||||
FnTrait::FnMut => {
|
||||
FnTrait::FnOnce | FnTrait::AsyncFnOnce => infer[expr].clone(),
|
||||
FnTrait::FnMut | FnTrait::AsyncFnMut => {
|
||||
TyKind::Ref(Mutability::Mut, error_lifetime(), infer[expr].clone()).intern(Interner)
|
||||
}
|
||||
FnTrait::Fn => {
|
||||
FnTrait::Fn | FnTrait::AsyncFn => {
|
||||
TyKind::Ref(Mutability::Not, error_lifetime(), infer[expr].clone()).intern(Interner)
|
||||
}
|
||||
},
|
||||
|
@ -2055,8 +2055,10 @@ pub fn mir_body_for_closure_query(
|
|||
let mut err = None;
|
||||
let closure_local = ctx.result.locals.iter().nth(1).unwrap().0;
|
||||
let closure_projection = match kind {
|
||||
FnTrait::FnOnce => vec![],
|
||||
FnTrait::FnMut | FnTrait::Fn => vec![ProjectionElem::Deref],
|
||||
FnTrait::FnOnce | FnTrait::AsyncFnOnce => vec![],
|
||||
FnTrait::FnMut | FnTrait::Fn | FnTrait::AsyncFnMut | FnTrait::AsyncFn => {
|
||||
vec![ProjectionElem::Deref]
|
||||
}
|
||||
};
|
||||
ctx.result.walk_places(|p, store| {
|
||||
if let Some(it) = upvar_map.get(&p.local) {
|
||||
|
|
|
@ -18,13 +18,13 @@ use std::sync::LazyLock;
|
|||
use base_db::SourceDatabaseFileInputExt as _;
|
||||
use expect_test::Expect;
|
||||
use hir_def::{
|
||||
body::{Body, BodySourceMap, SyntheticSyntax},
|
||||
body::{Body, BodySourceMap},
|
||||
db::DefDatabase,
|
||||
hir::{ExprId, Pat, PatId},
|
||||
item_scope::ItemScope,
|
||||
nameres::DefMap,
|
||||
src::HasSource,
|
||||
AssocItemId, DefWithBodyId, HasModule, LocalModuleId, Lookup, ModuleDefId,
|
||||
AssocItemId, DefWithBodyId, HasModule, LocalModuleId, Lookup, ModuleDefId, SyntheticSyntax,
|
||||
};
|
||||
use hir_expand::{db::ExpandDatabase, FileRange, InFile};
|
||||
use itertools::Itertools;
|
||||
|
|
|
@ -942,3 +942,19 @@ fn main() {
|
|||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_18626() {
|
||||
check_no_mismatches(
|
||||
r#"
|
||||
fn f() {
|
||||
trait T {
|
||||
fn f() {}
|
||||
}
|
||||
impl T for i32 {}
|
||||
impl T for u32 {}
|
||||
&[i32::f, u32::f] as &[fn()];
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1630,6 +1630,29 @@ fn test<'lifetime>(
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lifetime_bounds() {
|
||||
check_infer(
|
||||
r#"
|
||||
//- minicore: sized, coerce_unsized
|
||||
trait Trait<'a>: Sized {
|
||||
fn f(&'a self) {}
|
||||
}
|
||||
fn test<'a, 'b: 'a>(it: impl Trait<'a>){
|
||||
it.f();
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
38..42 'self': &'a Self
|
||||
44..46 '{}': ()
|
||||
69..71 'it': impl Trait<'a>
|
||||
88..103 '{ it.f(); }': ()
|
||||
94..96 'it': impl Trait<'a>
|
||||
94..100 'it.f()': ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_bound_chalk() {
|
||||
check_types(
|
||||
|
@ -4811,3 +4834,53 @@ fn bar(v: *const ()) {
|
|||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn async_fn_traits() {
|
||||
check_infer(
|
||||
r#"
|
||||
//- minicore: async_fn
|
||||
async fn foo<T: AsyncFn(u32) -> i32>(a: T) {
|
||||
let fut1 = a(0);
|
||||
fut1.await;
|
||||
}
|
||||
async fn bar<T: AsyncFnMut(u32) -> i32>(mut b: T) {
|
||||
let fut2 = b(0);
|
||||
fut2.await;
|
||||
}
|
||||
async fn baz<T: AsyncFnOnce(u32) -> i32>(c: T) {
|
||||
let fut3 = c(0);
|
||||
fut3.await;
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
37..38 'a': T
|
||||
43..83 '{ ...ait; }': ()
|
||||
43..83 '{ ...ait; }': impl Future<Output = ()>
|
||||
53..57 'fut1': AsyncFnMut::CallRefFuture<'?, T, (u32,)>
|
||||
60..61 'a': T
|
||||
60..64 'a(0)': AsyncFnMut::CallRefFuture<'?, T, (u32,)>
|
||||
62..63 '0': u32
|
||||
70..74 'fut1': AsyncFnMut::CallRefFuture<'?, T, (u32,)>
|
||||
70..80 'fut1.await': i32
|
||||
124..129 'mut b': T
|
||||
134..174 '{ ...ait; }': ()
|
||||
134..174 '{ ...ait; }': impl Future<Output = ()>
|
||||
144..148 'fut2': AsyncFnMut::CallRefFuture<'?, T, (u32,)>
|
||||
151..152 'b': T
|
||||
151..155 'b(0)': AsyncFnMut::CallRefFuture<'?, T, (u32,)>
|
||||
153..154 '0': u32
|
||||
161..165 'fut2': AsyncFnMut::CallRefFuture<'?, T, (u32,)>
|
||||
161..171 'fut2.await': i32
|
||||
216..217 'c': T
|
||||
222..262 '{ ...ait; }': ()
|
||||
222..262 '{ ...ait; }': impl Future<Output = ()>
|
||||
232..236 'fut3': AsyncFnOnce::CallOnceFuture<T, (u32,)>
|
||||
239..240 'c': T
|
||||
239..243 'c(0)': AsyncFnOnce::CallOnceFuture<T, (u32,)>
|
||||
241..242 '0': u32
|
||||
249..253 'fut3': AsyncFnOnce::CallOnceFuture<T, (u32,)>
|
||||
249..259 'fut3.await': i32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -220,6 +220,10 @@ pub enum FnTrait {
|
|||
FnOnce,
|
||||
FnMut,
|
||||
Fn,
|
||||
|
||||
AsyncFnOnce,
|
||||
AsyncFnMut,
|
||||
AsyncFn,
|
||||
}
|
||||
|
||||
impl fmt::Display for FnTrait {
|
||||
|
@ -228,6 +232,9 @@ impl fmt::Display for FnTrait {
|
|||
FnTrait::FnOnce => write!(f, "FnOnce"),
|
||||
FnTrait::FnMut => write!(f, "FnMut"),
|
||||
FnTrait::Fn => write!(f, "Fn"),
|
||||
FnTrait::AsyncFnOnce => write!(f, "AsyncFnOnce"),
|
||||
FnTrait::AsyncFnMut => write!(f, "AsyncFnMut"),
|
||||
FnTrait::AsyncFn => write!(f, "AsyncFn"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -238,6 +245,9 @@ impl FnTrait {
|
|||
FnTrait::FnOnce => "call_once",
|
||||
FnTrait::FnMut => "call_mut",
|
||||
FnTrait::Fn => "call",
|
||||
FnTrait::AsyncFnOnce => "async_call_once",
|
||||
FnTrait::AsyncFnMut => "async_call_mut",
|
||||
FnTrait::AsyncFn => "async_call",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,6 +256,9 @@ impl FnTrait {
|
|||
FnTrait::FnOnce => LangItem::FnOnce,
|
||||
FnTrait::FnMut => LangItem::FnMut,
|
||||
FnTrait::Fn => LangItem::Fn,
|
||||
FnTrait::AsyncFnOnce => LangItem::AsyncFnOnce,
|
||||
FnTrait::AsyncFnMut => LangItem::AsyncFnMut,
|
||||
FnTrait::AsyncFn => LangItem::AsyncFn,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -254,15 +267,19 @@ impl FnTrait {
|
|||
LangItem::FnOnce => Some(FnTrait::FnOnce),
|
||||
LangItem::FnMut => Some(FnTrait::FnMut),
|
||||
LangItem::Fn => Some(FnTrait::Fn),
|
||||
LangItem::AsyncFnOnce => Some(FnTrait::AsyncFnOnce),
|
||||
LangItem::AsyncFnMut => Some(FnTrait::AsyncFnMut),
|
||||
LangItem::AsyncFn => Some(FnTrait::AsyncFn),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn to_chalk_ir(self) -> rust_ir::ClosureKind {
|
||||
// Chalk doesn't support async fn traits.
|
||||
match self {
|
||||
FnTrait::FnOnce => rust_ir::ClosureKind::FnOnce,
|
||||
FnTrait::FnMut => rust_ir::ClosureKind::FnMut,
|
||||
FnTrait::Fn => rust_ir::ClosureKind::Fn,
|
||||
FnTrait::AsyncFnOnce | FnTrait::FnOnce => rust_ir::ClosureKind::FnOnce,
|
||||
FnTrait::AsyncFnMut | FnTrait::FnMut => rust_ir::ClosureKind::FnMut,
|
||||
FnTrait::AsyncFn | FnTrait::Fn => rust_ir::ClosureKind::Fn,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -271,6 +288,9 @@ impl FnTrait {
|
|||
FnTrait::FnOnce => Name::new_symbol_root(sym::call_once.clone()),
|
||||
FnTrait::FnMut => Name::new_symbol_root(sym::call_mut.clone()),
|
||||
FnTrait::Fn => Name::new_symbol_root(sym::call.clone()),
|
||||
FnTrait::AsyncFnOnce => Name::new_symbol_root(sym::async_call_once.clone()),
|
||||
FnTrait::AsyncFnMut => Name::new_symbol_root(sym::async_call_mut.clone()),
|
||||
FnTrait::AsyncFn => Name::new_symbol_root(sym::async_call.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -185,7 +185,7 @@ fn direct_super_traits_cb(db: &dyn DefDatabase, trait_: TraitId, cb: impl FnMut(
|
|||
}
|
||||
};
|
||||
match is_trait {
|
||||
true => bound.as_path(),
|
||||
true => bound.as_path(&generic_params.types_map),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,14 @@ syntax.workspace = true
|
|||
tt.workspace = true
|
||||
span.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
expect-test.workspace = true
|
||||
|
||||
# local deps
|
||||
test-utils.workspace = true
|
||||
test-fixture.workspace = true
|
||||
syntax-bridge.workspace = true
|
||||
|
||||
[features]
|
||||
in-rust-tree = ["hir-expand/in-rust-tree"]
|
||||
|
||||
|
|
|
@ -3,21 +3,35 @@
|
|||
//!
|
||||
//! This probably isn't the best way to do this -- ideally, diagnostics should
|
||||
//! be expressed in terms of hir types themselves.
|
||||
pub use hir_ty::diagnostics::{CaseType, IncorrectCase};
|
||||
use hir_ty::{
|
||||
db::HirDatabase, diagnostics::BodyValidationDiagnostic, CastError, InferenceDiagnostic,
|
||||
};
|
||||
|
||||
use cfg::{CfgExpr, CfgOptions};
|
||||
use either::Either;
|
||||
pub use hir_def::VariantId;
|
||||
use hir_def::{body::SyntheticSyntax, hir::ExprOrPatId, path::ModPath, AssocItemId, DefWithBodyId};
|
||||
use hir_def::{
|
||||
hir::ExprOrPatId,
|
||||
path::{hir_segment_to_ast_segment, ModPath},
|
||||
type_ref::TypesSourceMap,
|
||||
AssocItemId, DefWithBodyId, SyntheticSyntax,
|
||||
};
|
||||
use hir_expand::{name::Name, HirFileId, InFile};
|
||||
use syntax::{ast, AstPtr, SyntaxError, SyntaxNodePtr, TextRange};
|
||||
use hir_ty::{
|
||||
db::HirDatabase,
|
||||
diagnostics::{BodyValidationDiagnostic, UnsafetyReason},
|
||||
CastError, InferenceDiagnostic, InferenceTyDiagnosticSource, TyLoweringDiagnostic,
|
||||
TyLoweringDiagnosticKind,
|
||||
};
|
||||
use syntax::{
|
||||
ast::{self, HasGenericArgs},
|
||||
AstPtr, SyntaxError, SyntaxNodePtr, TextRange,
|
||||
};
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{AssocItem, Field, Local, Trait, Type};
|
||||
|
||||
pub use hir_def::VariantId;
|
||||
pub use hir_ty::{
|
||||
diagnostics::{CaseType, IncorrectCase},
|
||||
GenericArgsProhibitedReason,
|
||||
};
|
||||
|
||||
macro_rules! diagnostics {
|
||||
($($diag:ident,)*) => {
|
||||
#[derive(Debug)]
|
||||
|
@ -96,6 +110,7 @@ diagnostics![
|
|||
UnresolvedIdent,
|
||||
UnusedMut,
|
||||
UnusedVariable,
|
||||
GenericArgsProhibited,
|
||||
];
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -258,9 +273,10 @@ pub struct PrivateField {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct MissingUnsafe {
|
||||
pub expr: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
|
||||
pub node: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
|
||||
/// If true, the diagnostics is an `unsafe_op_in_unsafe_fn` lint instead of a hard error.
|
||||
pub only_lint: bool,
|
||||
pub reason: UnsafetyReason,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -385,6 +401,12 @@ pub struct InvalidCast {
|
|||
pub cast_ty: Type,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GenericArgsProhibited {
|
||||
pub args: InFile<AstPtr<Either<ast::GenericArgList, ast::ParenthesizedArgList>>>,
|
||||
pub reason: GenericArgsProhibitedReason,
|
||||
}
|
||||
|
||||
impl AnyDiagnostic {
|
||||
pub(crate) fn body_validation_diagnostic(
|
||||
db: &dyn HirDatabase,
|
||||
|
@ -524,6 +546,7 @@ impl AnyDiagnostic {
|
|||
db: &dyn HirDatabase,
|
||||
def: DefWithBodyId,
|
||||
d: &InferenceDiagnostic,
|
||||
outer_types_source_map: &TypesSourceMap,
|
||||
source_map: &hir_def::body::BodySourceMap,
|
||||
) -> Option<AnyDiagnostic> {
|
||||
let expr_syntax = |expr| {
|
||||
|
@ -637,6 +660,44 @@ impl AnyDiagnostic {
|
|||
let cast_ty = Type::new(db, def, cast_ty.clone());
|
||||
InvalidCast { expr, error: *error, expr_ty, cast_ty }.into()
|
||||
}
|
||||
InferenceDiagnostic::TyDiagnostic { source, diag } => {
|
||||
let source_map = match source {
|
||||
InferenceTyDiagnosticSource::Body => &source_map.types,
|
||||
InferenceTyDiagnosticSource::Signature => outer_types_source_map,
|
||||
};
|
||||
Self::ty_diagnostic(diag, source_map, db)?
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn ty_diagnostic(
|
||||
diag: &TyLoweringDiagnostic,
|
||||
source_map: &TypesSourceMap,
|
||||
db: &dyn HirDatabase,
|
||||
) -> Option<AnyDiagnostic> {
|
||||
let source = match diag.source {
|
||||
Either::Left(type_ref_id) => {
|
||||
let Ok(source) = source_map.type_syntax(type_ref_id) else {
|
||||
stdx::never!("error on synthetic type syntax");
|
||||
return None;
|
||||
};
|
||||
source
|
||||
}
|
||||
Either::Right(source) => source,
|
||||
};
|
||||
let syntax = || source.value.to_node(&db.parse_or_expand(source.file_id));
|
||||
Some(match diag.kind {
|
||||
TyLoweringDiagnosticKind::GenericArgsProhibited { segment, reason } => {
|
||||
let ast::Type::PathType(syntax) = syntax() else { return None };
|
||||
let segment = hir_segment_to_ast_segment(&syntax.path()?, segment)?;
|
||||
let args = if let Some(generics) = segment.generic_arg_list() {
|
||||
AstPtr::new(&generics).wrap_left()
|
||||
} else {
|
||||
AstPtr::new(&segment.parenthesized_arg_list()?).wrap_right()
|
||||
};
|
||||
let args = source.with_value(args);
|
||||
GenericArgsProhibited { args, reason }.into()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -132,12 +132,18 @@ impl HirDisplay for Function {
|
|||
} else {
|
||||
match &data.types_map[data.ret_type] {
|
||||
TypeRef::ImplTrait(bounds) => match &bounds[0] {
|
||||
TypeBound::Path(path, _) => Some(
|
||||
*path.segments().iter().last().unwrap().args_and_bindings.unwrap().bindings
|
||||
[0]
|
||||
.type_ref
|
||||
.as_ref()
|
||||
.unwrap(),
|
||||
&TypeBound::Path(path, _) => Some(
|
||||
*data.types_map[path]
|
||||
.segments()
|
||||
.iter()
|
||||
.last()
|
||||
.unwrap()
|
||||
.args_and_bindings
|
||||
.unwrap()
|
||||
.bindings[0]
|
||||
.type_ref
|
||||
.as_ref()
|
||||
.unwrap(),
|
||||
),
|
||||
_ => None,
|
||||
},
|
||||
|
|
|
@ -20,12 +20,11 @@
|
|||
#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
|
||||
#![recursion_limit = "512"]
|
||||
|
||||
mod semantics;
|
||||
mod source_analyzer;
|
||||
|
||||
mod attrs;
|
||||
mod from_id;
|
||||
mod has_source;
|
||||
mod semantics;
|
||||
mod source_analyzer;
|
||||
|
||||
pub mod db;
|
||||
pub mod diagnostics;
|
||||
|
@ -43,7 +42,7 @@ use arrayvec::ArrayVec;
|
|||
use base_db::{CrateDisplayName, CrateId, CrateOrigin};
|
||||
use either::Either;
|
||||
use hir_def::{
|
||||
body::{BodyDiagnostic, SyntheticSyntax},
|
||||
body::BodyDiagnostic,
|
||||
data::adt::VariantData,
|
||||
generics::{LifetimeParamData, TypeOrConstParamData, TypeParamProvenance},
|
||||
hir::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, LabelId, Pat},
|
||||
|
@ -54,11 +53,12 @@ use hir_def::{
|
|||
path::ImportAlias,
|
||||
per_ns::PerNs,
|
||||
resolver::{HasResolver, Resolver},
|
||||
type_ref::TypesSourceMap,
|
||||
AssocItemId, AssocItemLoc, AttrDefId, CallableDefId, ConstId, ConstParamId, CrateRootModuleId,
|
||||
DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FunctionId, GenericDefId, GenericParamId,
|
||||
HasModule, ImplId, InTypeConstId, ItemContainerId, LifetimeParamId, LocalFieldId, Lookup,
|
||||
MacroExpander, ModuleId, StaticId, StructId, TraitAliasId, TraitId, TupleId, TypeAliasId,
|
||||
TypeOrConstParamId, TypeParamId, UnionId,
|
||||
MacroExpander, ModuleId, StaticId, StructId, SyntheticSyntax, TraitAliasId, TraitId, TupleId,
|
||||
TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId,
|
||||
};
|
||||
use hir_expand::{
|
||||
attrs::collect_attrs, proc_macro::ProcMacroKind, AstId, MacroCallKind, RenderedExpandError,
|
||||
|
@ -76,8 +76,8 @@ use hir_ty::{
|
|||
traits::FnTrait,
|
||||
AliasTy, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId, GenericArg,
|
||||
GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, Substitution,
|
||||
TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, ValueTyDefId,
|
||||
WhereClause,
|
||||
TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, TyLoweringDiagnostic,
|
||||
ValueTyDefId, WhereClause,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use nameres::diagnostics::DefDiagnosticKind;
|
||||
|
@ -89,7 +89,7 @@ use syntax::{
|
|||
ast::{self, HasAttrs as _, HasGenericParams, HasName},
|
||||
format_smolstr, AstNode, AstPtr, SmolStr, SyntaxNode, SyntaxNodePtr, TextRange, ToSmolStr, T,
|
||||
};
|
||||
use triomphe::Arc;
|
||||
use triomphe::{Arc, ThinArc};
|
||||
|
||||
use crate::db::{DefDatabase, HirDatabase};
|
||||
|
||||
|
@ -147,6 +147,7 @@ pub use {
|
|||
},
|
||||
hir_ty::{
|
||||
consteval::ConstEvalError,
|
||||
diagnostics::UnsafetyReason,
|
||||
display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite},
|
||||
dyn_compatibility::{DynCompatibilityViolation, MethodViolationCode},
|
||||
layout::LayoutError,
|
||||
|
@ -410,6 +411,10 @@ impl ModuleDef {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(def) = self.as_self_generic_def() {
|
||||
def.diagnostics(db, &mut acc);
|
||||
}
|
||||
|
||||
acc
|
||||
}
|
||||
|
||||
|
@ -430,6 +435,23 @@ impl ModuleDef {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns only defs that have generics from themselves, not their parent.
|
||||
pub fn as_self_generic_def(self) -> Option<GenericDef> {
|
||||
match self {
|
||||
ModuleDef::Function(it) => Some(it.into()),
|
||||
ModuleDef::Adt(it) => Some(it.into()),
|
||||
ModuleDef::Trait(it) => Some(it.into()),
|
||||
ModuleDef::TraitAlias(it) => Some(it.into()),
|
||||
ModuleDef::TypeAlias(it) => Some(it.into()),
|
||||
ModuleDef::Module(_)
|
||||
| ModuleDef::Variant(_)
|
||||
| ModuleDef::Static(_)
|
||||
| ModuleDef::Const(_)
|
||||
| ModuleDef::BuiltinType(_)
|
||||
| ModuleDef::Macro(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attrs(&self, db: &dyn HirDatabase) -> Option<AttrsWithOwner> {
|
||||
Some(match self {
|
||||
ModuleDef::Module(it) => it.attrs(db),
|
||||
|
@ -604,17 +626,42 @@ impl Module {
|
|||
ModuleDef::Adt(adt) => {
|
||||
match adt {
|
||||
Adt::Struct(s) => {
|
||||
let tree_id = s.id.lookup(db.upcast()).id;
|
||||
let tree_source_maps = tree_id.item_tree_with_source_map(db.upcast()).1;
|
||||
push_ty_diagnostics(
|
||||
db,
|
||||
acc,
|
||||
db.field_types_with_diagnostics(s.id.into()).1,
|
||||
tree_source_maps.strukt(tree_id.value).item(),
|
||||
);
|
||||
for diag in db.struct_data_with_diagnostics(s.id).1.iter() {
|
||||
emit_def_diagnostic(db, acc, diag, edition);
|
||||
}
|
||||
}
|
||||
Adt::Union(u) => {
|
||||
let tree_id = u.id.lookup(db.upcast()).id;
|
||||
let tree_source_maps = tree_id.item_tree_with_source_map(db.upcast()).1;
|
||||
push_ty_diagnostics(
|
||||
db,
|
||||
acc,
|
||||
db.field_types_with_diagnostics(u.id.into()).1,
|
||||
tree_source_maps.union(tree_id.value).item(),
|
||||
);
|
||||
for diag in db.union_data_with_diagnostics(u.id).1.iter() {
|
||||
emit_def_diagnostic(db, acc, diag, edition);
|
||||
}
|
||||
}
|
||||
Adt::Enum(e) => {
|
||||
for v in e.variants(db) {
|
||||
let tree_id = v.id.lookup(db.upcast()).id;
|
||||
let tree_source_maps =
|
||||
tree_id.item_tree_with_source_map(db.upcast()).1;
|
||||
push_ty_diagnostics(
|
||||
db,
|
||||
acc,
|
||||
db.field_types_with_diagnostics(v.id.into()).1,
|
||||
tree_source_maps.variant(tree_id.value),
|
||||
);
|
||||
acc.extend(ModuleDef::Variant(v).diagnostics(db, style_lints));
|
||||
for diag in db.enum_variant_data_with_diagnostics(v.id).1.iter() {
|
||||
emit_def_diagnostic(db, acc, diag, edition);
|
||||
|
@ -625,6 +672,17 @@ impl Module {
|
|||
acc.extend(def.diagnostics(db, style_lints))
|
||||
}
|
||||
ModuleDef::Macro(m) => emit_macro_def_diagnostics(db, acc, m),
|
||||
ModuleDef::TypeAlias(type_alias) => {
|
||||
let tree_id = type_alias.id.lookup(db.upcast()).id;
|
||||
let tree_source_maps = tree_id.item_tree_with_source_map(db.upcast()).1;
|
||||
push_ty_diagnostics(
|
||||
db,
|
||||
acc,
|
||||
db.type_for_type_alias_with_diagnostics(type_alias.id).1,
|
||||
tree_source_maps.type_alias(tree_id.value).item(),
|
||||
);
|
||||
acc.extend(def.diagnostics(db, style_lints));
|
||||
}
|
||||
_ => acc.extend(def.diagnostics(db, style_lints)),
|
||||
}
|
||||
}
|
||||
|
@ -634,8 +692,11 @@ impl Module {
|
|||
|
||||
let mut impl_assoc_items_scratch = vec![];
|
||||
for impl_def in self.impl_defs(db) {
|
||||
GenericDef::Impl(impl_def).diagnostics(db, acc);
|
||||
|
||||
let loc = impl_def.id.lookup(db.upcast());
|
||||
let tree = loc.id.item_tree(db.upcast());
|
||||
let (tree, tree_source_maps) = loc.id.item_tree_with_source_map(db.upcast());
|
||||
let source_map = tree_source_maps.impl_(loc.id.value).item();
|
||||
let node = &tree[loc.id.value];
|
||||
let file_id = loc.id.file_id();
|
||||
if file_id.macro_file().map_or(false, |it| it.is_builtin_derive(db.upcast())) {
|
||||
|
@ -770,6 +831,19 @@ impl Module {
|
|||
impl_assoc_items_scratch.clear();
|
||||
}
|
||||
|
||||
push_ty_diagnostics(
|
||||
db,
|
||||
acc,
|
||||
db.impl_self_ty_with_diagnostics(impl_def.id).1,
|
||||
source_map,
|
||||
);
|
||||
push_ty_diagnostics(
|
||||
db,
|
||||
acc,
|
||||
db.impl_trait_with_diagnostics(impl_def.id).and_then(|it| it.1),
|
||||
source_map,
|
||||
);
|
||||
|
||||
for &item in db.impl_data(impl_def.id).items.iter() {
|
||||
AssocItem::from(item).diagnostics(db, acc, style_lints);
|
||||
}
|
||||
|
@ -1801,6 +1875,25 @@ impl DefWithBody {
|
|||
let krate = self.module(db).id.krate();
|
||||
|
||||
let (body, source_map) = db.body_with_source_map(self.into());
|
||||
let item_tree_source_maps;
|
||||
let outer_types_source_map = match self {
|
||||
DefWithBody::Function(function) => {
|
||||
let function = function.id.lookup(db.upcast()).id;
|
||||
item_tree_source_maps = function.item_tree_with_source_map(db.upcast()).1;
|
||||
item_tree_source_maps.function(function.value).item()
|
||||
}
|
||||
DefWithBody::Static(statik) => {
|
||||
let statik = statik.id.lookup(db.upcast()).id;
|
||||
item_tree_source_maps = statik.item_tree_with_source_map(db.upcast()).1;
|
||||
item_tree_source_maps.statik(statik.value)
|
||||
}
|
||||
DefWithBody::Const(konst) => {
|
||||
let konst = konst.id.lookup(db.upcast()).id;
|
||||
item_tree_source_maps = konst.item_tree_with_source_map(db.upcast()).1;
|
||||
item_tree_source_maps.konst(konst.value)
|
||||
}
|
||||
DefWithBody::Variant(_) | DefWithBody::InTypeConst(_) => &TypesSourceMap::EMPTY,
|
||||
};
|
||||
|
||||
for (_, def_map) in body.blocks(db.upcast()) {
|
||||
Module { id: def_map.module_id(DefMap::ROOT) }.diagnostics(db, acc, style_lints);
|
||||
|
@ -1860,7 +1953,13 @@ impl DefWithBody {
|
|||
|
||||
let infer = db.infer(self.into());
|
||||
for d in &infer.diagnostics {
|
||||
acc.extend(AnyDiagnostic::inference_diagnostic(db, self.into(), d, &source_map));
|
||||
acc.extend(AnyDiagnostic::inference_diagnostic(
|
||||
db,
|
||||
self.into(),
|
||||
d,
|
||||
outer_types_source_map,
|
||||
&source_map,
|
||||
));
|
||||
}
|
||||
|
||||
for (pat_or_expr, mismatch) in infer.type_mismatches() {
|
||||
|
@ -1890,10 +1989,10 @@ impl DefWithBody {
|
|||
);
|
||||
}
|
||||
|
||||
let (unafe_exprs, only_lint) = hir_ty::diagnostics::missing_unsafe(db, self.into());
|
||||
for expr in unafe_exprs {
|
||||
match source_map.expr_or_pat_syntax(expr) {
|
||||
Ok(expr) => acc.push(MissingUnsafe { expr, only_lint }.into()),
|
||||
let (unsafe_exprs, only_lint) = hir_ty::diagnostics::missing_unsafe(db, self.into());
|
||||
for (node, reason) in unsafe_exprs {
|
||||
match source_map.expr_or_pat_syntax(node) {
|
||||
Ok(node) => acc.push(MissingUnsafe { node, only_lint, reason }.into()),
|
||||
Err(SyntheticSyntax) => {
|
||||
// FIXME: Here and elsewhere in this file, the `expr` was
|
||||
// desugared, report or assert that this doesn't happen.
|
||||
|
@ -3324,12 +3423,22 @@ impl AssocItem {
|
|||
) {
|
||||
match self {
|
||||
AssocItem::Function(func) => {
|
||||
GenericDef::Function(func).diagnostics(db, acc);
|
||||
DefWithBody::from(func).diagnostics(db, acc, style_lints);
|
||||
}
|
||||
AssocItem::Const(const_) => {
|
||||
DefWithBody::from(const_).diagnostics(db, acc, style_lints);
|
||||
}
|
||||
AssocItem::TypeAlias(type_alias) => {
|
||||
GenericDef::TypeAlias(type_alias).diagnostics(db, acc);
|
||||
let tree_id = type_alias.id.lookup(db.upcast()).id;
|
||||
let tree_source_maps = tree_id.item_tree_with_source_map(db.upcast()).1;
|
||||
push_ty_diagnostics(
|
||||
db,
|
||||
acc,
|
||||
db.type_for_type_alias_with_diagnostics(type_alias.id).1,
|
||||
tree_source_maps.type_alias(tree_id.value).item(),
|
||||
);
|
||||
for diag in hir_ty::diagnostics::incorrect_case(db, type_alias.id.into()) {
|
||||
acc.push(diag.into());
|
||||
}
|
||||
|
@ -3416,6 +3525,97 @@ impl GenericDef {
|
|||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn id(self) -> GenericDefId {
|
||||
match self {
|
||||
GenericDef::Function(it) => it.id.into(),
|
||||
GenericDef::Adt(it) => it.into(),
|
||||
GenericDef::Trait(it) => it.id.into(),
|
||||
GenericDef::TraitAlias(it) => it.id.into(),
|
||||
GenericDef::TypeAlias(it) => it.id.into(),
|
||||
GenericDef::Impl(it) => it.id.into(),
|
||||
GenericDef::Const(it) => it.id.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>) {
|
||||
let def = self.id();
|
||||
|
||||
let item_tree_source_maps;
|
||||
let (generics, generics_source_map) = db.generic_params_with_source_map(def);
|
||||
|
||||
if generics.is_empty() && generics.no_predicates() {
|
||||
return;
|
||||
}
|
||||
|
||||
let source_map = match &generics_source_map {
|
||||
Some(it) => it,
|
||||
None => match def {
|
||||
GenericDefId::FunctionId(it) => {
|
||||
let id = it.lookup(db.upcast()).id;
|
||||
item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1;
|
||||
item_tree_source_maps.function(id.value).generics()
|
||||
}
|
||||
GenericDefId::AdtId(AdtId::EnumId(it)) => {
|
||||
let id = it.lookup(db.upcast()).id;
|
||||
item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1;
|
||||
item_tree_source_maps.enum_generic(id.value)
|
||||
}
|
||||
GenericDefId::AdtId(AdtId::StructId(it)) => {
|
||||
let id = it.lookup(db.upcast()).id;
|
||||
item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1;
|
||||
item_tree_source_maps.strukt(id.value).generics()
|
||||
}
|
||||
GenericDefId::AdtId(AdtId::UnionId(it)) => {
|
||||
let id = it.lookup(db.upcast()).id;
|
||||
item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1;
|
||||
item_tree_source_maps.union(id.value).generics()
|
||||
}
|
||||
GenericDefId::TraitId(it) => {
|
||||
let id = it.lookup(db.upcast()).id;
|
||||
item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1;
|
||||
item_tree_source_maps.trait_generic(id.value)
|
||||
}
|
||||
GenericDefId::TraitAliasId(it) => {
|
||||
let id = it.lookup(db.upcast()).id;
|
||||
item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1;
|
||||
item_tree_source_maps.trait_alias_generic(id.value)
|
||||
}
|
||||
GenericDefId::TypeAliasId(it) => {
|
||||
let id = it.lookup(db.upcast()).id;
|
||||
item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1;
|
||||
item_tree_source_maps.type_alias(id.value).generics()
|
||||
}
|
||||
GenericDefId::ImplId(it) => {
|
||||
let id = it.lookup(db.upcast()).id;
|
||||
item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1;
|
||||
item_tree_source_maps.impl_(id.value).generics()
|
||||
}
|
||||
GenericDefId::ConstId(_) => return,
|
||||
},
|
||||
};
|
||||
|
||||
push_ty_diagnostics(db, acc, db.generic_defaults_with_diagnostics(def).1, source_map);
|
||||
push_ty_diagnostics(
|
||||
db,
|
||||
acc,
|
||||
db.generic_predicates_without_parent_with_diagnostics(def).1,
|
||||
source_map,
|
||||
);
|
||||
for (param_id, param) in generics.iter_type_or_consts() {
|
||||
if let TypeOrConstParamData::ConstParamData(_) = param {
|
||||
push_ty_diagnostics(
|
||||
db,
|
||||
acc,
|
||||
db.const_param_ty_with_diagnostics(ConstParamId::from_unchecked(
|
||||
TypeOrConstParamId { parent: def, local_id: param_id },
|
||||
))
|
||||
.1,
|
||||
source_map,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A single local definition.
|
||||
|
@ -3581,6 +3781,18 @@ impl Local {
|
|||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Local {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Local {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.binding_id.cmp(&other.binding_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct DeriveHelper {
|
||||
pub(crate) derive: MacroId,
|
||||
|
@ -5799,3 +6011,19 @@ pub enum DocLinkDef {
|
|||
Field(Field),
|
||||
SelfType(Trait),
|
||||
}
|
||||
|
||||
fn push_ty_diagnostics(
|
||||
db: &dyn HirDatabase,
|
||||
acc: &mut Vec<AnyDiagnostic>,
|
||||
diagnostics: Option<ThinArc<(), TyLoweringDiagnostic>>,
|
||||
source_map: &TypesSourceMap,
|
||||
) {
|
||||
if let Some(diagnostics) = diagnostics {
|
||||
acc.extend(
|
||||
diagnostics
|
||||
.slice
|
||||
.iter()
|
||||
.filter_map(|diagnostic| AnyDiagnostic::ty_diagnostic(diagnostic, source_map, db)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ use intern::Symbol;
|
|||
use itertools::Itertools;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use span::{EditionedFileId, FileId, HirFileIdRepr, SyntaxContextId};
|
||||
use span::{AstIdMap, EditionedFileId, FileId, HirFileIdRepr, SyntaxContextId};
|
||||
use stdx::TupleExt;
|
||||
use syntax::{
|
||||
algo::skip_trivia_token,
|
||||
|
@ -42,6 +42,7 @@ use syntax::{
|
|||
AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange,
|
||||
TextSize,
|
||||
};
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
db::HirDatabase,
|
||||
|
@ -509,6 +510,22 @@ impl<'db> SemanticsImpl<'db> {
|
|||
self.with_ctx(|ctx| ctx.has_derives(adt))
|
||||
}
|
||||
|
||||
pub fn derive_helpers_in_scope(&self, adt: &ast::Adt) -> Option<Vec<(Symbol, Symbol)>> {
|
||||
let sa = self.analyze_no_infer(adt.syntax())?;
|
||||
let id = self.db.ast_id_map(sa.file_id).ast_id(adt);
|
||||
let result = sa
|
||||
.resolver
|
||||
.def_map()
|
||||
.derive_helpers_in_scope(InFile::new(sa.file_id, id))?
|
||||
.iter()
|
||||
.map(|(name, macro_, _)| {
|
||||
let macro_name = Macro::from(*macro_).name(self.db).symbol().clone();
|
||||
(name.symbol().clone(), macro_name)
|
||||
})
|
||||
.collect();
|
||||
Some(result)
|
||||
}
|
||||
|
||||
pub fn derive_helper(&self, attr: &ast::Attr) -> Option<Vec<(Macro, MacroFileId)>> {
|
||||
let adt = attr.syntax().ancestors().find_map(ast::Item::cast).and_then(|it| match it {
|
||||
ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
|
||||
|
@ -1500,6 +1517,10 @@ impl<'db> SemanticsImpl<'db> {
|
|||
self.analyze(path.syntax())?.resolve_path(self.db, path)
|
||||
}
|
||||
|
||||
pub fn resolve_use_type_arg(&self, name: &ast::NameRef) -> Option<TypeParam> {
|
||||
self.analyze(name.syntax())?.resolve_use_type_arg(name)
|
||||
}
|
||||
|
||||
pub fn resolve_mod_path(
|
||||
&self,
|
||||
scope: &SyntaxNode,
|
||||
|
@ -1973,10 +1994,16 @@ impl SemanticsScope<'_> {
|
|||
/// Resolve a path as-if it was written at the given scope. This is
|
||||
/// necessary a heuristic, as it doesn't take hygiene into account.
|
||||
pub fn speculative_resolve(&self, ast_path: &ast::Path) -> Option<PathResolution> {
|
||||
let root = ast_path.syntax().ancestors().last().unwrap();
|
||||
let ast_id_map = Arc::new(AstIdMap::from_source(&root));
|
||||
let (mut types_map, mut types_source_map) =
|
||||
(TypesMap::default(), TypesSourceMap::default());
|
||||
let mut ctx =
|
||||
LowerCtx::new(self.db.upcast(), self.file_id, &mut types_map, &mut types_source_map);
|
||||
let mut ctx = LowerCtx::for_synthetic_ast(
|
||||
self.db.upcast(),
|
||||
ast_id_map,
|
||||
&mut types_map,
|
||||
&mut types_source_map,
|
||||
);
|
||||
let path = Path::from_src(&mut ctx, ast_path.clone())?;
|
||||
resolve_hir_path(
|
||||
self.db,
|
||||
|
@ -2003,6 +2030,10 @@ impl SemanticsScope<'_> {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn generic_def(&self) -> Option<crate::GenericDef> {
|
||||
self.resolver.generic_def().map(|id| id.into())
|
||||
}
|
||||
|
||||
pub fn extern_crates(&self) -> impl Iterator<Item = (Name, Module)> + '_ {
|
||||
self.resolver.extern_crates_in_scope().map(|(name, id)| (name, Module { id }))
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ use hir_expand::{
|
|||
use hir_ty::{
|
||||
diagnostics::{
|
||||
record_literal_missing_fields, record_pattern_missing_fields, unsafe_expressions,
|
||||
UnsafeExpr,
|
||||
InsideUnsafeBlock,
|
||||
},
|
||||
lang_items::lang_items_for_bin_op,
|
||||
method_resolution, Adjustment, InferenceResult, Interner, Substitution, Ty, TyExt, TyKind,
|
||||
|
@ -642,6 +642,14 @@ impl SourceAnalyzer {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_use_type_arg(&self, name: &ast::NameRef) -> Option<crate::TypeParam> {
|
||||
let name = name.as_name();
|
||||
self.resolver
|
||||
.all_generic_params()
|
||||
.find_map(|(params, parent)| params.find_type_by_name(&name, *parent))
|
||||
.map(crate::TypeParam::from)
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_path(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
|
@ -939,8 +947,8 @@ impl SourceAnalyzer {
|
|||
*def,
|
||||
body,
|
||||
expr_id,
|
||||
&mut |UnsafeExpr { inside_unsafe_block, .. }| {
|
||||
is_unsafe |= !inside_unsafe_block
|
||||
&mut |_, inside_unsafe_block, _| {
|
||||
is_unsafe |= inside_unsafe_block == InsideUnsafeBlock::No
|
||||
},
|
||||
)
|
||||
};
|
||||
|
|
|
@ -2318,4 +2318,49 @@ impl<'a> Test<'a, i32> for bool {
|
|||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_17321() {
|
||||
check_assist(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
fn main() {}
|
||||
|
||||
mod other_file_1 {
|
||||
pub const SOME_CONSTANT: usize = 8;
|
||||
}
|
||||
|
||||
mod other_file_2 {
|
||||
use crate::other_file_1::SOME_CONSTANT;
|
||||
|
||||
pub trait Trait {
|
||||
type Iter: Iterator<Item = [u8; SOME_CONSTANT]>;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MyStruct;
|
||||
|
||||
impl other_file_2::Trait for MyStruct$0 {}"#,
|
||||
r#"
|
||||
fn main() {}
|
||||
|
||||
mod other_file_1 {
|
||||
pub const SOME_CONSTANT: usize = 8;
|
||||
}
|
||||
|
||||
mod other_file_2 {
|
||||
use crate::other_file_1::SOME_CONSTANT;
|
||||
|
||||
pub trait Trait {
|
||||
type Iter: Iterator<Item = [u8; SOME_CONSTANT]>;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MyStruct;
|
||||
|
||||
impl other_file_2::Trait for MyStruct {
|
||||
$0type Iter;
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use either::Either;
|
||||
use ide_db::defs::{Definition, NameRefClass};
|
||||
use syntax::{
|
||||
ast::{self, make, HasArgList, HasGenericArgs},
|
||||
ted, AstNode,
|
||||
ast::{self, make, syntax_factory::SyntaxFactory, HasArgList, HasGenericArgs},
|
||||
syntax_editor::Position,
|
||||
AstNode,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
@ -91,20 +92,34 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
|
|||
AssistId("add_type_ascription", AssistKind::RefactorRewrite),
|
||||
"Add `: _` before assignment operator",
|
||||
ident.text_range(),
|
||||
|edit| {
|
||||
let let_stmt = edit.make_mut(let_stmt);
|
||||
|builder| {
|
||||
let mut editor = builder.make_editor(let_stmt.syntax());
|
||||
|
||||
if let_stmt.semicolon_token().is_none() {
|
||||
ted::append_child(let_stmt.syntax(), make::tokens::semicolon());
|
||||
editor.insert(
|
||||
Position::last_child_of(let_stmt.syntax()),
|
||||
make::tokens::semicolon(),
|
||||
);
|
||||
}
|
||||
|
||||
let placeholder_ty = make::ty_placeholder().clone_for_update();
|
||||
|
||||
let_stmt.set_ty(Some(placeholder_ty.clone()));
|
||||
|
||||
if let Some(cap) = ctx.config.snippet_cap {
|
||||
edit.add_placeholder_snippet(cap, placeholder_ty);
|
||||
if let Some(pat) = let_stmt.pat() {
|
||||
let elements = vec![
|
||||
make::token(syntax::SyntaxKind::COLON).into(),
|
||||
make::token(syntax::SyntaxKind::WHITESPACE).into(),
|
||||
placeholder_ty.syntax().clone().into(),
|
||||
];
|
||||
editor.insert_all(Position::after(pat.syntax()), elements);
|
||||
if let Some(cap) = ctx.config.snippet_cap {
|
||||
editor.add_annotation(
|
||||
placeholder_ty.syntax(),
|
||||
builder.make_placeholder_snippet(cap),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
builder.add_file_edits(ctx.file_id(), editor);
|
||||
},
|
||||
)?
|
||||
} else {
|
||||
|
@ -123,38 +138,58 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
|
|||
AssistId("add_turbo_fish", AssistKind::RefactorRewrite),
|
||||
"Add `::<>`",
|
||||
ident.text_range(),
|
||||
|edit| {
|
||||
edit.trigger_parameter_hints();
|
||||
|builder| {
|
||||
builder.trigger_parameter_hints();
|
||||
|
||||
let new_arg_list = match turbofish_target {
|
||||
let make = SyntaxFactory::new();
|
||||
let mut editor = match &turbofish_target {
|
||||
Either::Left(it) => builder.make_editor(it.syntax()),
|
||||
Either::Right(it) => builder.make_editor(it.syntax()),
|
||||
};
|
||||
|
||||
let fish_head = get_fish_head(&make, number_of_arguments);
|
||||
|
||||
match turbofish_target {
|
||||
Either::Left(path_segment) => {
|
||||
edit.make_mut(path_segment).get_or_create_generic_arg_list()
|
||||
if let Some(generic_arg_list) = path_segment.generic_arg_list() {
|
||||
editor.replace(generic_arg_list.syntax(), fish_head.syntax());
|
||||
} else {
|
||||
editor.insert(
|
||||
Position::last_child_of(path_segment.syntax()),
|
||||
fish_head.syntax(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Either::Right(method_call) => {
|
||||
edit.make_mut(method_call).get_or_create_generic_arg_list()
|
||||
if let Some(generic_arg_list) = method_call.generic_arg_list() {
|
||||
editor.replace(generic_arg_list.syntax(), fish_head.syntax());
|
||||
} else {
|
||||
let position = if let Some(arg_list) = method_call.arg_list() {
|
||||
Position::before(arg_list.syntax())
|
||||
} else {
|
||||
Position::last_child_of(method_call.syntax())
|
||||
};
|
||||
editor.insert(position, fish_head.syntax());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let fish_head = get_fish_head(number_of_arguments).clone_for_update();
|
||||
|
||||
// Note: we need to replace the `new_arg_list` instead of being able to use something like
|
||||
// `GenericArgList::add_generic_arg` as `PathSegment::get_or_create_generic_arg_list`
|
||||
// always creates a non-turbofish form generic arg list.
|
||||
ted::replace(new_arg_list.syntax(), fish_head.syntax());
|
||||
|
||||
if let Some(cap) = ctx.config.snippet_cap {
|
||||
for arg in fish_head.generic_args() {
|
||||
edit.add_placeholder_snippet(cap, arg)
|
||||
editor.add_annotation(arg.syntax(), builder.make_placeholder_snippet(cap));
|
||||
}
|
||||
}
|
||||
|
||||
editor.add_mappings(make.finish_with_mappings());
|
||||
builder.add_file_edits(ctx.file_id(), editor);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// This will create a turbofish generic arg list corresponding to the number of arguments
|
||||
fn get_fish_head(number_of_arguments: usize) -> ast::GenericArgList {
|
||||
fn get_fish_head(make: &SyntaxFactory, number_of_arguments: usize) -> ast::GenericArgList {
|
||||
let args = (0..number_of_arguments).map(|_| make::type_arg(make::ty_placeholder()).into());
|
||||
make::turbofish_generic_arg_list(args)
|
||||
make.turbofish_generic_arg_list(args)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -512,9 +512,11 @@ fn make_bool_enum(make_pub: bool) -> ast::Enum {
|
|||
let enum_def = make::enum_(
|
||||
if make_pub { Some(make::visibility_pub()) } else { None },
|
||||
make::name("Bool"),
|
||||
None,
|
||||
None,
|
||||
make::variant_list(vec![
|
||||
make::variant(make::name("True"), None),
|
||||
make::variant(make::name("False"), None),
|
||||
make::variant(None, make::name("True"), None, None),
|
||||
make::variant(None, make::name("False"), None, None),
|
||||
]),
|
||||
)
|
||||
.clone_for_update();
|
||||
|
|
|
@ -18,6 +18,7 @@ use ide_db::{
|
|||
},
|
||||
FxIndexSet, RootDatabase,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use syntax::{
|
||||
ast::{
|
||||
self, edit::IndentLevel, edit_in_place::Indent, AstNode, AstToken, HasGenericParams,
|
||||
|
@ -114,8 +115,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
|
|||
return;
|
||||
}
|
||||
|
||||
let params =
|
||||
body.extracted_function_params(ctx, &container_info, locals_used.iter().copied());
|
||||
let params = body.extracted_function_params(ctx, &container_info, locals_used);
|
||||
|
||||
let name = make_function_name(&semantics_scope);
|
||||
|
||||
|
@ -1067,9 +1067,11 @@ impl FunctionBody {
|
|||
&self,
|
||||
ctx: &AssistContext<'_>,
|
||||
container_info: &ContainerInfo,
|
||||
locals: impl Iterator<Item = Local>,
|
||||
locals: FxIndexSet<Local>,
|
||||
) -> Vec<Param> {
|
||||
locals
|
||||
.into_iter()
|
||||
.sorted()
|
||||
.map(|local| (local, local.primary_source(ctx.db())))
|
||||
.filter(|(_, src)| is_defined_outside_of_body(ctx, self, src))
|
||||
.filter_map(|(local, src)| match src.into_ident_pat() {
|
||||
|
@ -3167,11 +3169,11 @@ fn foo() {
|
|||
let mut c = C { p: P { n: 0 } };
|
||||
let mut v = C { p: P { n: 0 } };
|
||||
let u = C { p: P { n: 0 } };
|
||||
fun_name(&mut c, &u, &mut v);
|
||||
fun_name(&mut c, &mut v, &u);
|
||||
let m = c.p.n + v.p.n + u.p.n;
|
||||
}
|
||||
|
||||
fn $0fun_name(c: &mut C, u: &C, v: &mut C) {
|
||||
fn $0fun_name(c: &mut C, v: &mut C, u: &C) {
|
||||
c.p.n += u.p.n;
|
||||
let r = &mut v.p.n;
|
||||
}
|
||||
|
@ -5602,10 +5604,10 @@ fn parent(factor: i32) {
|
|||
fn parent(factor: i32) {
|
||||
let v = &[1, 2, 3];
|
||||
|
||||
fun_name(v, factor);
|
||||
fun_name(factor, v);
|
||||
}
|
||||
|
||||
fn $0fun_name(v: &[i32; 3], factor: i32) {
|
||||
fn $0fun_name(factor: i32, v: &[i32; 3]) {
|
||||
v.iter().map(|it| it * factor);
|
||||
}
|
||||
"#,
|
||||
|
@ -5786,11 +5788,11 @@ struct Struct<T: Into<i32>>(T);
|
|||
impl <T: Into<i32> + Copy> Struct<T> {
|
||||
fn func<V: Into<i32>>(&self, v: V) -> i32 {
|
||||
let t = self.0;
|
||||
fun_name(t, v)
|
||||
fun_name(v, t)
|
||||
}
|
||||
}
|
||||
|
||||
fn $0fun_name<T: Into<i32> + Copy, V: Into<i32>>(t: T, v: V) -> i32 {
|
||||
fn $0fun_name<T: Into<i32> + Copy, V: Into<i32>>(v: V, t: T) -> i32 {
|
||||
t.into() + v.into()
|
||||
}
|
||||
"#,
|
||||
|
@ -5815,11 +5817,11 @@ struct Struct<T: Into<i32>, U: Debug>(T, U);
|
|||
impl <T: Into<i32> + Copy, U: Debug> Struct<T, U> {
|
||||
fn func<V: Into<i32>>(&self, v: V) -> i32 {
|
||||
let t = self.0;
|
||||
fun_name(t, v)
|
||||
fun_name(v, t)
|
||||
}
|
||||
}
|
||||
|
||||
fn $0fun_name<T: Into<i32> + Copy, V: Into<i32>>(t: T, v: V) -> i32 {
|
||||
fn $0fun_name<T: Into<i32> + Copy, V: Into<i32>>(v: V, t: T) -> i32 {
|
||||
t.into() + v.into()
|
||||
}
|
||||
"#,
|
||||
|
@ -5844,11 +5846,11 @@ struct Struct<T>(T) where T: Into<i32>;
|
|||
impl <T> Struct<T> where T: Into<i32> + Copy {
|
||||
fn func<V>(&self, v: V) -> i32 where V: Into<i32> {
|
||||
let t = self.0;
|
||||
fun_name(t, v)
|
||||
fun_name(v, t)
|
||||
}
|
||||
}
|
||||
|
||||
fn $0fun_name<T, V>(t: T, v: V) -> i32 where T: Into<i32> + Copy, V: Into<i32> {
|
||||
fn $0fun_name<T, V>(v: V, t: T) -> i32 where T: Into<i32> + Copy, V: Into<i32> {
|
||||
t.into() + v.into()
|
||||
}
|
||||
"#,
|
||||
|
@ -5873,11 +5875,11 @@ struct Struct<T, U>(T, U) where T: Into<i32>, U: Debug;
|
|||
impl <T, U> Struct<T, U> where T: Into<i32> + Copy, U: Debug {
|
||||
fn func<V>(&self, v: V) -> i32 where V: Into<i32> {
|
||||
let t = self.0;
|
||||
fun_name(t, v)
|
||||
fun_name(v, t)
|
||||
}
|
||||
}
|
||||
|
||||
fn $0fun_name<T, V>(t: T, v: V) -> i32 where T: Into<i32> + Copy, V: Into<i32> {
|
||||
fn $0fun_name<T, V>(v: V, t: T) -> i32 where T: Into<i32> + Copy, V: Into<i32> {
|
||||
t.into() + v.into()
|
||||
}
|
||||
"#,
|
||||
|
@ -6106,6 +6108,31 @@ fn $0fun_name() -> i32 {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sort_params_in_order() {
|
||||
check_assist(
|
||||
extract_function,
|
||||
r#"
|
||||
fn existing(a: i32, b: i32, c: i32) {
|
||||
let x = 32;
|
||||
|
||||
let p = $0x + b + c + a$0;
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn existing(a: i32, b: i32, c: i32) {
|
||||
let x = 32;
|
||||
|
||||
let p = fun_name(a, b, c, x);
|
||||
}
|
||||
|
||||
fn $0fun_name(a: i32, b: i32, c: i32, x: i32) -> i32 {
|
||||
x + b + c + a
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn in_left_curly_is_not_applicable() {
|
||||
cov_mark::check!(extract_function_in_braces_is_not_applicable);
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use syntax::ast::{self, AstNode, BinExpr};
|
||||
use syntax::{
|
||||
ast::{self, syntax_factory::SyntaxFactory, AstNode, BinExpr},
|
||||
SyntaxKind, T,
|
||||
};
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
|
@ -19,22 +22,17 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
|
|||
// ```
|
||||
pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
|
||||
let expr = ctx.find_node_at_offset::<BinExpr>()?;
|
||||
let rhs = expr.rhs()?.syntax().clone();
|
||||
let lhs = expr.lhs()?.syntax().clone();
|
||||
let lhs = expr.lhs()?;
|
||||
let rhs = expr.rhs()?;
|
||||
|
||||
let lhs = if let Some(bin_expr) = BinExpr::cast(lhs.clone()) {
|
||||
if bin_expr.op_kind() == expr.op_kind() {
|
||||
bin_expr.rhs()?.syntax().clone()
|
||||
} else {
|
||||
lhs
|
||||
}
|
||||
} else {
|
||||
lhs
|
||||
let lhs = match &lhs {
|
||||
ast::Expr::BinExpr(bin_expr) if bin_expr.op_kind() == expr.op_kind() => bin_expr.rhs()?,
|
||||
_ => lhs,
|
||||
};
|
||||
|
||||
let op_range = expr.op_token()?.text_range();
|
||||
let op_token = expr.op_token()?;
|
||||
// The assist should be applied only if the cursor is on the operator
|
||||
let cursor_in_range = op_range.contains_range(ctx.selection_trimmed());
|
||||
let cursor_in_range = op_token.text_range().contains_range(ctx.selection_trimmed());
|
||||
if !cursor_in_range {
|
||||
return None;
|
||||
}
|
||||
|
@ -47,13 +45,17 @@ pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
|
|||
acc.add(
|
||||
AssistId("flip_binexpr", AssistKind::RefactorRewrite),
|
||||
"Flip binary expression",
|
||||
op_range,
|
||||
|edit| {
|
||||
if let FlipAction::FlipAndReplaceOp(new_op) = action {
|
||||
edit.replace(op_range, new_op);
|
||||
}
|
||||
edit.replace(lhs.text_range(), rhs.text());
|
||||
edit.replace(rhs.text_range(), lhs.text());
|
||||
op_token.text_range(),
|
||||
|builder| {
|
||||
let mut editor = builder.make_editor(&expr.syntax().parent().unwrap());
|
||||
let make = SyntaxFactory::new();
|
||||
if let FlipAction::FlipAndReplaceOp(binary_op) = action {
|
||||
editor.replace(op_token, make.token(binary_op))
|
||||
};
|
||||
editor.replace(lhs.syntax(), rhs.syntax());
|
||||
editor.replace(rhs.syntax(), lhs.syntax());
|
||||
editor.add_mappings(make.finish_with_mappings());
|
||||
builder.add_file_edits(ctx.file_id(), editor);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -62,7 +64,7 @@ enum FlipAction {
|
|||
// Flip the expression
|
||||
Flip,
|
||||
// Flip the expression and replace the operator with this string
|
||||
FlipAndReplaceOp(&'static str),
|
||||
FlipAndReplaceOp(SyntaxKind),
|
||||
// Do not flip the expression
|
||||
DontFlip,
|
||||
}
|
||||
|
@ -73,10 +75,10 @@ impl From<ast::BinaryOp> for FlipAction {
|
|||
ast::BinaryOp::Assignment { .. } => FlipAction::DontFlip,
|
||||
ast::BinaryOp::CmpOp(ast::CmpOp::Ord { ordering, strict }) => {
|
||||
let rev_op = match (ordering, strict) {
|
||||
(ast::Ordering::Less, true) => ">",
|
||||
(ast::Ordering::Less, false) => ">=",
|
||||
(ast::Ordering::Greater, true) => "<",
|
||||
(ast::Ordering::Greater, false) => "<=",
|
||||
(ast::Ordering::Less, true) => T![>],
|
||||
(ast::Ordering::Less, false) => T![>=],
|
||||
(ast::Ordering::Greater, true) => T![<],
|
||||
(ast::Ordering::Greater, false) => T![<=],
|
||||
};
|
||||
FlipAction::FlipAndReplaceOp(rev_op)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use ide_db::base_db::SourceDatabase;
|
||||
use syntax::TextSize;
|
||||
use syntax::{
|
||||
algo::non_trivia_sibling, ast, AstNode, Direction, SyntaxKind, SyntaxToken, TextRange, T,
|
||||
algo::non_trivia_sibling,
|
||||
ast::{self, syntax_factory::SyntaxFactory},
|
||||
syntax_editor::{Element, SyntaxMapping},
|
||||
AstNode, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxToken, T,
|
||||
};
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
@ -25,8 +26,6 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
|
|||
let comma = ctx.find_token_syntax_at_offset(T![,])?;
|
||||
let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
|
||||
let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
|
||||
let (mut prev_text, mut next_text) = (prev.to_string(), next.to_string());
|
||||
let (mut prev_range, mut next_range) = (prev.text_range(), next.text_range());
|
||||
|
||||
// Don't apply a "flip" in case of a last comma
|
||||
// that typically comes before punctuation
|
||||
|
@ -40,53 +39,84 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
|
|||
return None;
|
||||
}
|
||||
|
||||
if let Some(parent) = comma.parent().and_then(ast::TokenTree::cast) {
|
||||
// An attribute. It often contains a path followed by a token tree (e.g. `align(2)`), so we have
|
||||
// to be smarter.
|
||||
let prev_start =
|
||||
match comma.siblings_with_tokens(Direction::Prev).skip(1).find(|it| it.kind() == T![,])
|
||||
{
|
||||
Some(it) => position_after_token(it.as_token().unwrap()),
|
||||
None => position_after_token(&parent.left_delimiter_token()?),
|
||||
};
|
||||
let prev_end = prev.text_range().end();
|
||||
let next_start = next.text_range().start();
|
||||
let next_end =
|
||||
match comma.siblings_with_tokens(Direction::Next).skip(1).find(|it| it.kind() == T![,])
|
||||
{
|
||||
Some(it) => position_before_token(it.as_token().unwrap()),
|
||||
None => position_before_token(&parent.right_delimiter_token()?),
|
||||
};
|
||||
prev_range = TextRange::new(prev_start, prev_end);
|
||||
next_range = TextRange::new(next_start, next_end);
|
||||
let file_text = ctx.db().file_text(ctx.file_id().file_id());
|
||||
prev_text = file_text[prev_range].to_owned();
|
||||
next_text = file_text[next_range].to_owned();
|
||||
}
|
||||
let prev = match prev {
|
||||
SyntaxElement::Node(node) => node.syntax_element(),
|
||||
_ => prev,
|
||||
};
|
||||
let next = match next {
|
||||
SyntaxElement::Node(node) => node.syntax_element(),
|
||||
_ => next,
|
||||
};
|
||||
|
||||
acc.add(
|
||||
AssistId("flip_comma", AssistKind::RefactorRewrite),
|
||||
"Flip comma",
|
||||
comma.text_range(),
|
||||
|edit| {
|
||||
edit.replace(prev_range, next_text);
|
||||
edit.replace(next_range, prev_text);
|
||||
|builder| {
|
||||
let parent = comma.parent().unwrap();
|
||||
let mut editor = builder.make_editor(&parent);
|
||||
|
||||
if let Some(parent) = ast::TokenTree::cast(parent) {
|
||||
// An attribute. It often contains a path followed by a
|
||||
// token tree (e.g. `align(2)`), so we have to be smarter.
|
||||
let (new_tree, mapping) = flip_tree(parent.clone(), comma);
|
||||
editor.replace(parent.syntax(), new_tree.syntax());
|
||||
editor.add_mappings(mapping);
|
||||
} else {
|
||||
editor.replace(prev.clone(), next.clone());
|
||||
editor.replace(next.clone(), prev.clone());
|
||||
}
|
||||
|
||||
builder.add_file_edits(ctx.file_id(), editor);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn position_before_token(token: &SyntaxToken) -> TextSize {
|
||||
match non_trivia_sibling(token.clone().into(), Direction::Prev) {
|
||||
Some(prev_token) => prev_token.text_range().end(),
|
||||
None => token.text_range().start(),
|
||||
}
|
||||
}
|
||||
fn flip_tree(tree: ast::TokenTree, comma: SyntaxToken) -> (ast::TokenTree, SyntaxMapping) {
|
||||
let mut tree_iter = tree.token_trees_and_tokens();
|
||||
let before: Vec<_> =
|
||||
tree_iter.by_ref().take_while(|it| it.as_token() != Some(&comma)).collect();
|
||||
let after: Vec<_> = tree_iter.collect();
|
||||
|
||||
fn position_after_token(token: &SyntaxToken) -> TextSize {
|
||||
match non_trivia_sibling(token.clone().into(), Direction::Next) {
|
||||
Some(prev_token) => prev_token.text_range().start(),
|
||||
None => token.text_range().end(),
|
||||
}
|
||||
let not_ws = |element: &NodeOrToken<_, SyntaxToken>| match element {
|
||||
NodeOrToken::Token(token) => token.kind() != SyntaxKind::WHITESPACE,
|
||||
NodeOrToken::Node(_) => true,
|
||||
};
|
||||
|
||||
let is_comma = |element: &NodeOrToken<_, SyntaxToken>| match element {
|
||||
NodeOrToken::Token(token) => token.kind() == T![,],
|
||||
NodeOrToken::Node(_) => false,
|
||||
};
|
||||
|
||||
let prev_start_untrimmed = match before.iter().rposition(is_comma) {
|
||||
Some(pos) => pos + 1,
|
||||
None => 1,
|
||||
};
|
||||
let prev_end = 1 + before.iter().rposition(not_ws).unwrap();
|
||||
let prev_start = prev_start_untrimmed
|
||||
+ before[prev_start_untrimmed..prev_end].iter().position(not_ws).unwrap();
|
||||
|
||||
let next_start = after.iter().position(not_ws).unwrap();
|
||||
let next_end_untrimmed = match after.iter().position(is_comma) {
|
||||
Some(pos) => pos,
|
||||
None => after.len() - 1,
|
||||
};
|
||||
let next_end = 1 + after[..next_end_untrimmed].iter().rposition(not_ws).unwrap();
|
||||
|
||||
let result = [
|
||||
&before[1..prev_start],
|
||||
&after[next_start..next_end],
|
||||
&before[prev_end..],
|
||||
&[NodeOrToken::Token(comma)],
|
||||
&after[..next_start],
|
||||
&before[prev_start..prev_end],
|
||||
&after[next_end..after.len() - 1],
|
||||
]
|
||||
.concat();
|
||||
|
||||
let make = SyntaxFactory::new();
|
||||
let new_token_tree = make.token_tree(tree.left_delimiter_token().unwrap().kind(), result);
|
||||
(new_token_tree, make.finish_with_mappings())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -147,4 +177,9 @@ mod tests {
|
|||
r#"#[foo(bar, qux, baz(1 + 1), other)] struct Foo;"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip_comma_attribute_incomplete() {
|
||||
check_assist_not_applicable(flip_comma, r#"#[repr(align(2),$0)] struct Foo;"#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,11 +23,11 @@ pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
|
|||
let plus = ctx.find_token_syntax_at_offset(T![+])?;
|
||||
|
||||
// Make sure we're in a `TypeBoundList`
|
||||
ast::TypeBoundList::cast(plus.parent()?)?;
|
||||
let parent = ast::TypeBoundList::cast(plus.parent()?)?;
|
||||
|
||||
let (before, after) = (
|
||||
non_trivia_sibling(plus.clone().into(), Direction::Prev)?,
|
||||
non_trivia_sibling(plus.clone().into(), Direction::Next)?,
|
||||
non_trivia_sibling(plus.clone().into(), Direction::Prev)?.into_node()?,
|
||||
non_trivia_sibling(plus.clone().into(), Direction::Next)?.into_node()?,
|
||||
);
|
||||
|
||||
let target = plus.text_range();
|
||||
|
@ -35,9 +35,11 @@ pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
|
|||
AssistId("flip_trait_bound", AssistKind::RefactorRewrite),
|
||||
"Flip trait bounds",
|
||||
target,
|
||||
|edit| {
|
||||
edit.replace(before.text_range(), after.to_string());
|
||||
edit.replace(after.text_range(), before.to_string());
|
||||
|builder| {
|
||||
let mut editor = builder.make_editor(parent.syntax());
|
||||
editor.replace(before.clone(), after.clone());
|
||||
editor.replace(after, before);
|
||||
builder.add_file_edits(ctx.file_id(), editor);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ pub(crate) fn generate_documentation_template(
|
|||
// /// # Examples
|
||||
// ///
|
||||
// /// ```
|
||||
// /// use test::add;
|
||||
// /// use ra_test_fixture::add;
|
||||
// ///
|
||||
// /// assert_eq!(add(a, b), );
|
||||
// /// ```
|
||||
|
@ -596,7 +596,7 @@ pub fn noop_with_param(_a: i32) {}
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use test::noop_with_param;
|
||||
/// use ra_test_fixture::noop_with_param;
|
||||
///
|
||||
/// noop_with_param(_a);
|
||||
/// ```
|
||||
|
@ -641,7 +641,7 @@ pub unsafe fn noop_unsafe() {}
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use test::noop_unsafe;
|
||||
/// use ra_test_fixture::noop_unsafe;
|
||||
///
|
||||
/// unsafe { noop_unsafe() };
|
||||
/// ```
|
||||
|
@ -758,7 +758,7 @@ pub fn returns_a_value$0() -> i32 {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use test::returns_a_value;
|
||||
/// use ra_test_fixture::returns_a_value;
|
||||
///
|
||||
/// assert_eq!(returns_a_value(), );
|
||||
/// ```
|
||||
|
@ -807,7 +807,7 @@ pub fn modifies_a_value$0(a: &mut i32) {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use test::modifies_a_value;
|
||||
/// use ra_test_fixture::modifies_a_value;
|
||||
///
|
||||
/// let mut a = ;
|
||||
/// modifies_a_value(&mut a);
|
||||
|
@ -836,7 +836,7 @@ pub fn sum3$0(a: i32, b: i32, c: i32) -> i32 {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use test::sum3;
|
||||
/// use ra_test_fixture::sum3;
|
||||
///
|
||||
/// let result = sum3(a, b, c);
|
||||
/// assert_eq!(result, );
|
||||
|
@ -868,7 +868,7 @@ pub mod a {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use test::a::b::noop;
|
||||
/// use ra_test_fixture::a::b::noop;
|
||||
///
|
||||
/// noop();
|
||||
/// ```
|
||||
|
@ -898,7 +898,7 @@ impl MyStruct {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use test::MyStruct;
|
||||
/// use ra_test_fixture::MyStruct;
|
||||
///
|
||||
/// MyStruct::noop();
|
||||
/// ```
|
||||
|
@ -1169,7 +1169,7 @@ impl<T> MyGenericStruct<T> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use test::MyGenericStruct;
|
||||
/// use ra_test_fixture::MyGenericStruct;
|
||||
///
|
||||
/// let my_generic_struct = ;
|
||||
/// my_generic_struct.consume();
|
||||
|
@ -1199,7 +1199,7 @@ impl<T> MyGenericStruct<T> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use test::MyGenericStruct;
|
||||
/// use ra_test_fixture::MyGenericStruct;
|
||||
///
|
||||
/// let mut my_generic_struct = ;
|
||||
/// my_generic_struct.modify(new_value);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use hir::{HasSource, HirDisplay, InRealFile};
|
||||
use ide_db::assists::{AssistId, AssistKind};
|
||||
use syntax::{
|
||||
ast::{self, make, HasArgList},
|
||||
ast::{self, syntax_factory::SyntaxFactory, HasArgList},
|
||||
match_ast, AstNode, SyntaxNode,
|
||||
};
|
||||
|
||||
|
@ -33,7 +33,7 @@ use crate::assist_context::{AssistContext, Assists};
|
|||
// ```
|
||||
pub(crate) fn generate_enum_variant(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
|
||||
let path: ast::Path = ctx.find_node_at_offset()?;
|
||||
let parent = path_parent(&path)?;
|
||||
let parent = PathParent::new(&path)?;
|
||||
|
||||
if ctx.sema.resolve_path(&path).is_some() {
|
||||
// No need to generate anything if the path resolves
|
||||
|
@ -46,14 +46,32 @@ pub(crate) fn generate_enum_variant(acc: &mut Assists, ctx: &AssistContext<'_>)
|
|||
return None;
|
||||
}
|
||||
|
||||
if let Some(hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e)))) =
|
||||
let Some(hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e)))) =
|
||||
ctx.sema.resolve_path(&path.qualifier()?)
|
||||
{
|
||||
let target = path.syntax().text_range();
|
||||
return add_variant_to_accumulator(acc, ctx, target, e, &name_ref, parent);
|
||||
}
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
None
|
||||
let target = path.syntax().text_range();
|
||||
let name_ref: &ast::NameRef = &name_ref;
|
||||
let db = ctx.db();
|
||||
let InRealFile { file_id, value: enum_node } = e.source(db)?.original_ast_node_rooted(db)?;
|
||||
|
||||
acc.add(
|
||||
AssistId("generate_enum_variant", AssistKind::Generate),
|
||||
"Generate variant",
|
||||
target,
|
||||
|builder| {
|
||||
let mut editor = builder.make_editor(enum_node.syntax());
|
||||
let make = SyntaxFactory::new();
|
||||
let field_list = parent.make_field_list(ctx, &make);
|
||||
let variant = make.variant(None, make.name(&name_ref.text()), field_list, None);
|
||||
if let Some(it) = enum_node.variant_list() {
|
||||
it.add_variant(&mut editor, &variant);
|
||||
}
|
||||
builder.add_file_edits(file_id, editor);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -65,6 +83,20 @@ enum PathParent {
|
|||
}
|
||||
|
||||
impl PathParent {
|
||||
fn new(path: &ast::Path) -> Option<Self> {
|
||||
let parent = path.syntax().parent()?;
|
||||
|
||||
match_ast! {
|
||||
match parent {
|
||||
ast::PathExpr(it) => Some(PathParent::PathExpr(it)),
|
||||
ast::RecordExpr(it) => Some(PathParent::RecordExpr(it)),
|
||||
ast::PathPat(it) => Some(PathParent::PathPat(it)),
|
||||
ast::UseTree(it) => Some(PathParent::UseTree(it)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn syntax(&self) -> &SyntaxNode {
|
||||
match self {
|
||||
PathParent::PathExpr(it) => it.syntax(),
|
||||
|
@ -74,97 +106,49 @@ impl PathParent {
|
|||
}
|
||||
}
|
||||
|
||||
fn make_field_list(&self, ctx: &AssistContext<'_>) -> Option<ast::FieldList> {
|
||||
fn make_field_list(
|
||||
&self,
|
||||
ctx: &AssistContext<'_>,
|
||||
make: &SyntaxFactory,
|
||||
) -> Option<ast::FieldList> {
|
||||
let scope = ctx.sema.scope(self.syntax())?;
|
||||
|
||||
match self {
|
||||
PathParent::PathExpr(it) => {
|
||||
if let Some(call_expr) = it.syntax().parent().and_then(ast::CallExpr::cast) {
|
||||
make_tuple_field_list(call_expr, ctx, &scope)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let call_expr = ast::CallExpr::cast(it.syntax().parent()?)?;
|
||||
let args = call_expr.arg_list()?.args();
|
||||
let tuple_fields = args.map(|arg| {
|
||||
let ty =
|
||||
expr_ty(ctx, make, arg, &scope).unwrap_or_else(|| make.ty_infer().into());
|
||||
make.tuple_field(None, ty)
|
||||
});
|
||||
Some(make.tuple_field_list(tuple_fields).into())
|
||||
}
|
||||
PathParent::RecordExpr(it) => {
|
||||
let fields = it.record_expr_field_list()?.fields();
|
||||
let record_fields = fields.map(|field| {
|
||||
let name = name_from_field(make, &field);
|
||||
|
||||
let ty = field
|
||||
.expr()
|
||||
.and_then(|it| expr_ty(ctx, make, it, &scope))
|
||||
.unwrap_or_else(|| make.ty_infer().into());
|
||||
|
||||
make.record_field(None, name, ty)
|
||||
});
|
||||
Some(make.record_field_list(record_fields).into())
|
||||
}
|
||||
PathParent::RecordExpr(it) => make_record_field_list(it, ctx, &scope),
|
||||
PathParent::UseTree(_) | PathParent::PathPat(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn path_parent(path: &ast::Path) -> Option<PathParent> {
|
||||
let parent = path.syntax().parent()?;
|
||||
|
||||
match_ast! {
|
||||
match parent {
|
||||
ast::PathExpr(it) => Some(PathParent::PathExpr(it)),
|
||||
ast::RecordExpr(it) => Some(PathParent::RecordExpr(it)),
|
||||
ast::PathPat(it) => Some(PathParent::PathPat(it)),
|
||||
ast::UseTree(it) => Some(PathParent::UseTree(it)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_variant_to_accumulator(
|
||||
acc: &mut Assists,
|
||||
ctx: &AssistContext<'_>,
|
||||
target: syntax::TextRange,
|
||||
adt: hir::Enum,
|
||||
name_ref: &ast::NameRef,
|
||||
parent: PathParent,
|
||||
) -> Option<()> {
|
||||
let db = ctx.db();
|
||||
let InRealFile { file_id, value: enum_node } = adt.source(db)?.original_ast_node_rooted(db)?;
|
||||
|
||||
acc.add(
|
||||
AssistId("generate_enum_variant", AssistKind::Generate),
|
||||
"Generate variant",
|
||||
target,
|
||||
|builder| {
|
||||
builder.edit_file(file_id.file_id());
|
||||
let node = builder.make_mut(enum_node);
|
||||
let variant = make_variant(ctx, name_ref, parent);
|
||||
if let Some(it) = node.variant_list() {
|
||||
it.add_variant(variant.clone_for_update())
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn make_variant(
|
||||
ctx: &AssistContext<'_>,
|
||||
name_ref: &ast::NameRef,
|
||||
parent: PathParent,
|
||||
) -> ast::Variant {
|
||||
let field_list = parent.make_field_list(ctx);
|
||||
make::variant(make::name(&name_ref.text()), field_list)
|
||||
}
|
||||
|
||||
fn make_record_field_list(
|
||||
record: &ast::RecordExpr,
|
||||
ctx: &AssistContext<'_>,
|
||||
scope: &hir::SemanticsScope<'_>,
|
||||
) -> Option<ast::FieldList> {
|
||||
let fields = record.record_expr_field_list()?.fields();
|
||||
let record_fields = fields.map(|field| {
|
||||
let name = name_from_field(&field);
|
||||
|
||||
let ty = field
|
||||
.expr()
|
||||
.and_then(|it| expr_ty(ctx, it, scope))
|
||||
.unwrap_or_else(make::ty_placeholder);
|
||||
|
||||
make::record_field(None, name, ty)
|
||||
});
|
||||
Some(make::record_field_list(record_fields).into())
|
||||
}
|
||||
|
||||
fn name_from_field(field: &ast::RecordExprField) -> ast::Name {
|
||||
fn name_from_field(make: &SyntaxFactory, field: &ast::RecordExprField) -> ast::Name {
|
||||
let text = match field.name_ref() {
|
||||
Some(it) => it.to_string(),
|
||||
None => name_from_field_shorthand(field).unwrap_or("unknown".to_owned()),
|
||||
};
|
||||
make::name(&text)
|
||||
make.name(&text)
|
||||
}
|
||||
|
||||
fn name_from_field_shorthand(field: &ast::RecordExprField) -> Option<String> {
|
||||
|
@ -175,27 +159,15 @@ fn name_from_field_shorthand(field: &ast::RecordExprField) -> Option<String> {
|
|||
Some(path.as_single_name_ref()?.to_string())
|
||||
}
|
||||
|
||||
fn make_tuple_field_list(
|
||||
call_expr: ast::CallExpr,
|
||||
ctx: &AssistContext<'_>,
|
||||
scope: &hir::SemanticsScope<'_>,
|
||||
) -> Option<ast::FieldList> {
|
||||
let args = call_expr.arg_list()?.args();
|
||||
let tuple_fields = args.map(|arg| {
|
||||
let ty = expr_ty(ctx, arg, scope).unwrap_or_else(make::ty_placeholder);
|
||||
make::tuple_field(None, ty)
|
||||
});
|
||||
Some(make::tuple_field_list(tuple_fields).into())
|
||||
}
|
||||
|
||||
fn expr_ty(
|
||||
ctx: &AssistContext<'_>,
|
||||
make: &SyntaxFactory,
|
||||
arg: ast::Expr,
|
||||
scope: &hir::SemanticsScope<'_>,
|
||||
) -> Option<ast::Type> {
|
||||
let ty = ctx.sema.type_of_expr(&arg).map(|it| it.adjusted())?;
|
||||
let text = ty.display_source_code(ctx.db(), scope.module().into(), false).ok()?;
|
||||
Some(make::ty(&text))
|
||||
Some(make.ty(&text))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
use ide_db::syntax_helpers::suggest_name;
|
||||
use itertools::Itertools;
|
||||
use syntax::{
|
||||
ast::{self, edit_in_place::GenericParamsOwnerEdit, make, AstNode, HasGenericParams, HasName},
|
||||
ted,
|
||||
};
|
||||
use syntax::ast::{self, syntax_factory::SyntaxFactory, AstNode, HasGenericParams, HasName};
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
|
@ -25,42 +22,42 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_>
|
|||
|
||||
let type_bound_list = impl_trait_type.type_bound_list()?;
|
||||
|
||||
let make = SyntaxFactory::new();
|
||||
let target = fn_.syntax().text_range();
|
||||
acc.add(
|
||||
AssistId("introduce_named_generic", AssistKind::RefactorRewrite),
|
||||
"Replace impl trait with generic",
|
||||
target,
|
||||
|edit| {
|
||||
let impl_trait_type = edit.make_mut(impl_trait_type);
|
||||
let fn_ = edit.make_mut(fn_);
|
||||
let fn_generic_param_list = fn_.get_or_create_generic_param_list();
|
||||
|builder| {
|
||||
let mut editor = builder.make_editor(fn_.syntax());
|
||||
|
||||
let existing_names = fn_generic_param_list
|
||||
.generic_params()
|
||||
.flat_map(|param| match param {
|
||||
ast::GenericParam::TypeParam(t) => t.name().map(|name| name.to_string()),
|
||||
p => Some(p.to_string()),
|
||||
})
|
||||
.collect_vec();
|
||||
let existing_names = match fn_.generic_param_list() {
|
||||
Some(generic_param_list) => generic_param_list
|
||||
.generic_params()
|
||||
.flat_map(|param| match param {
|
||||
ast::GenericParam::TypeParam(t) => t.name().map(|name| name.to_string()),
|
||||
p => Some(p.to_string()),
|
||||
})
|
||||
.collect_vec(),
|
||||
None => Vec::new(),
|
||||
};
|
||||
let type_param_name = suggest_name::NameGenerator::new_with_names(
|
||||
existing_names.iter().map(|s| s.as_str()),
|
||||
)
|
||||
.for_impl_trait_as_generic(&impl_trait_type);
|
||||
|
||||
let type_param = make::type_param(make::name(&type_param_name), Some(type_bound_list))
|
||||
.clone_for_update();
|
||||
let new_ty = make::ty(&type_param_name).clone_for_update();
|
||||
let type_param = make.type_param(make.name(&type_param_name), Some(type_bound_list));
|
||||
let new_ty = make.ty(&type_param_name);
|
||||
|
||||
ted::replace(impl_trait_type.syntax(), new_ty.syntax());
|
||||
fn_generic_param_list.add_generic_param(type_param.into());
|
||||
editor.replace(impl_trait_type.syntax(), new_ty.syntax());
|
||||
editor.add_generic_param(&fn_, type_param.clone().into());
|
||||
|
||||
if let Some(cap) = ctx.config.snippet_cap {
|
||||
if let Some(generic_param) =
|
||||
fn_.generic_param_list().and_then(|it| it.generic_params().last())
|
||||
{
|
||||
edit.add_tabstop_before(cap, generic_param);
|
||||
}
|
||||
editor.add_annotation(type_param.syntax(), builder.make_tabstop_before(cap));
|
||||
}
|
||||
|
||||
editor.add_mappings(make.finish_with_mappings());
|
||||
builder.add_file_edits(ctx.file_id(), editor);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -92,10 +92,9 @@ fn replace<T: AstNode + PartialEq>(
|
|||
fields: impl Iterator<Item = T>,
|
||||
sorted_fields: impl IntoIterator<Item = T>,
|
||||
) {
|
||||
fields.zip(sorted_fields).for_each(|(field, sorted_field)| {
|
||||
// FIXME: remove `clone_for_update` when `SyntaxEditor` handles it for us
|
||||
editor.replace(field.syntax(), sorted_field.syntax().clone_for_update())
|
||||
});
|
||||
fields
|
||||
.zip(sorted_fields)
|
||||
.for_each(|(field, sorted_field)| editor.replace(field.syntax(), sorted_field.syntax()));
|
||||
}
|
||||
|
||||
fn compute_fields_ranks(
|
||||
|
|
|
@ -101,10 +101,10 @@ pub(crate) fn reorder_impl_items(acc: &mut Assists, ctx: &AssistContext<'_>) ->
|
|||
|builder| {
|
||||
let mut editor = builder.make_editor(&parent_node);
|
||||
|
||||
assoc_items.into_iter().zip(sorted).for_each(|(old, new)| {
|
||||
// FIXME: remove `clone_for_update` when `SyntaxEditor` handles it for us
|
||||
editor.replace(old.syntax(), new.clone_for_update().syntax())
|
||||
});
|
||||
assoc_items
|
||||
.into_iter()
|
||||
.zip(sorted)
|
||||
.for_each(|(old, new)| editor.replace(old.syntax(), new.syntax()));
|
||||
|
||||
builder.add_file_edits(ctx.file_id(), editor);
|
||||
},
|
||||
|
|
|
@ -4,7 +4,7 @@ use itertools::Itertools;
|
|||
|
||||
use syntax::{
|
||||
ast::{self, HasName},
|
||||
ted, AstNode, TextRange,
|
||||
AstNode, SyntaxNode,
|
||||
};
|
||||
|
||||
use crate::{utils::get_methods, AssistContext, AssistId, AssistKind, Assists};
|
||||
|
@ -114,7 +114,7 @@ trait AddRewrite {
|
|||
label: &str,
|
||||
old: Vec<T>,
|
||||
new: Vec<T>,
|
||||
target: TextRange,
|
||||
target: &SyntaxNode,
|
||||
) -> Option<()>;
|
||||
}
|
||||
|
||||
|
@ -124,15 +124,22 @@ impl AddRewrite for Assists {
|
|||
label: &str,
|
||||
old: Vec<T>,
|
||||
new: Vec<T>,
|
||||
target: TextRange,
|
||||
target: &SyntaxNode,
|
||||
) -> Option<()> {
|
||||
self.add(AssistId("sort_items", AssistKind::RefactorRewrite), label, target, |builder| {
|
||||
let mutable: Vec<T> = old.into_iter().map(|it| builder.make_mut(it)).collect();
|
||||
mutable
|
||||
.into_iter()
|
||||
.zip(new)
|
||||
.for_each(|(old, new)| ted::replace(old.syntax(), new.clone_for_update().syntax()));
|
||||
})
|
||||
self.add(
|
||||
AssistId("sort_items", AssistKind::RefactorRewrite),
|
||||
label,
|
||||
target.text_range(),
|
||||
|builder| {
|
||||
let mut editor = builder.make_editor(target);
|
||||
|
||||
old.into_iter()
|
||||
.zip(new)
|
||||
.for_each(|(old, new)| editor.replace(old.syntax(), new.syntax()));
|
||||
|
||||
builder.add_file_edits(builder.file_id, editor)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,7 +174,7 @@ fn add_sort_methods_assist(
|
|||
return None;
|
||||
}
|
||||
|
||||
acc.add_rewrite("Sort methods alphabetically", methods, sorted, item_list.syntax().text_range())
|
||||
acc.add_rewrite("Sort methods alphabetically", methods, sorted, item_list.syntax())
|
||||
}
|
||||
|
||||
fn add_sort_fields_assist(
|
||||
|
@ -182,12 +189,7 @@ fn add_sort_fields_assist(
|
|||
return None;
|
||||
}
|
||||
|
||||
acc.add_rewrite(
|
||||
"Sort fields alphabetically",
|
||||
fields,
|
||||
sorted,
|
||||
record_field_list.syntax().text_range(),
|
||||
)
|
||||
acc.add_rewrite("Sort fields alphabetically", fields, sorted, record_field_list.syntax())
|
||||
}
|
||||
|
||||
fn add_sort_variants_assist(acc: &mut Assists, variant_list: ast::VariantList) -> Option<()> {
|
||||
|
@ -199,12 +201,7 @@ fn add_sort_variants_assist(acc: &mut Assists, variant_list: ast::VariantList) -
|
|||
return None;
|
||||
}
|
||||
|
||||
acc.add_rewrite(
|
||||
"Sort variants alphabetically",
|
||||
variants,
|
||||
sorted,
|
||||
variant_list.syntax().text_range(),
|
||||
)
|
||||
acc.add_rewrite("Sort variants alphabetically", variants, sorted, variant_list.syntax())
|
||||
}
|
||||
|
||||
fn sort_by_name<T: HasName + Clone>(initial: &[T]) -> Vec<T> {
|
||||
|
|
|
@ -1392,7 +1392,7 @@ pub fn add(a: i32, b: i32) -> i32 { a + b }
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use test::add;
|
||||
/// use ra_test_fixture::add;
|
||||
///
|
||||
/// assert_eq!(add(a, b), );
|
||||
/// ```
|
||||
|
|
|
@ -86,10 +86,21 @@ pub(crate) fn complete_attribute_path(
|
|||
acc: &mut Completions,
|
||||
ctx: &CompletionContext<'_>,
|
||||
path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx,
|
||||
&AttrCtx { kind, annotated_item_kind }: &AttrCtx,
|
||||
&AttrCtx { kind, annotated_item_kind, ref derive_helpers }: &AttrCtx,
|
||||
) {
|
||||
let is_inner = kind == AttrKind::Inner;
|
||||
|
||||
for (derive_helper, derive_name) in derive_helpers {
|
||||
let mut item = CompletionItem::new(
|
||||
SymbolKind::Attribute,
|
||||
ctx.source_range(),
|
||||
derive_helper.as_str(),
|
||||
ctx.edition,
|
||||
);
|
||||
item.detail(format!("derive helper of `{derive_name}`"));
|
||||
item.add_to(acc, ctx.db);
|
||||
}
|
||||
|
||||
match qualified {
|
||||
Qualified::With {
|
||||
resolution: Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))),
|
||||
|
|
|
@ -38,7 +38,6 @@ const SUPPORTED_CALLING_CONVENTIONS: &[&str] = &[
|
|||
"system-unwind",
|
||||
"rust-intrinsic",
|
||||
"rust-call",
|
||||
"platform-intrinsic",
|
||||
"unadjusted",
|
||||
];
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
//! show up for normal completions, or they won't show completions other than lifetimes depending
|
||||
//! on the fixture input.
|
||||
use hir::{sym, Name, ScopeDef};
|
||||
use syntax::{ast, ToSmolStr, TokenText};
|
||||
|
||||
use crate::{
|
||||
completions::Completions,
|
||||
|
@ -21,33 +20,24 @@ pub(crate) fn complete_lifetime(
|
|||
ctx: &CompletionContext<'_>,
|
||||
lifetime_ctx: &LifetimeContext,
|
||||
) {
|
||||
let (lp, lifetime) = match lifetime_ctx {
|
||||
LifetimeContext { kind: LifetimeKind::Lifetime, lifetime } => (None, lifetime),
|
||||
LifetimeContext {
|
||||
kind: LifetimeKind::LifetimeParam { is_decl: false, param },
|
||||
lifetime,
|
||||
} => (Some(param), lifetime),
|
||||
_ => return,
|
||||
let &LifetimeContext { kind: LifetimeKind::Lifetime { in_lifetime_param_bound, def }, .. } =
|
||||
lifetime_ctx
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let param_lifetime = match (lifetime, lp.and_then(|lp| lp.lifetime())) {
|
||||
(Some(lt), Some(lp)) if lp == lt.clone() => return,
|
||||
(Some(_), Some(lp)) => Some(lp),
|
||||
_ => None,
|
||||
};
|
||||
let param_lifetime = param_lifetime.as_ref().map(ast::Lifetime::text);
|
||||
let param_lifetime = param_lifetime.as_ref().map(TokenText::as_str);
|
||||
|
||||
ctx.process_all_names_raw(&mut |name, res| {
|
||||
if matches!(
|
||||
res,
|
||||
ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_))
|
||||
if param_lifetime != Some(&*name.display_no_db(ctx.edition).to_smolstr())
|
||||
) {
|
||||
if matches!(res, ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_))) {
|
||||
acc.add_lifetime(ctx, name);
|
||||
}
|
||||
});
|
||||
if param_lifetime.is_none() {
|
||||
acc.add_lifetime(ctx, Name::new_symbol_root(sym::tick_static.clone()));
|
||||
acc.add_lifetime(ctx, Name::new_symbol_root(sym::tick_static.clone()));
|
||||
if !in_lifetime_param_bound
|
||||
&& def.is_some_and(|def| {
|
||||
!matches!(def, hir::GenericDef::Function(_) | hir::GenericDef::Impl(_))
|
||||
})
|
||||
{
|
||||
acc.add_lifetime(ctx, Name::new_symbol_root(sym::tick_underscore.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -222,6 +212,8 @@ fn foo<'footime, 'lifetime: 'a$0>() {}
|
|||
"#,
|
||||
expect![[r#"
|
||||
lt 'footime
|
||||
lt 'lifetime
|
||||
lt 'static
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ mod tests;
|
|||
use std::{iter, ops::ControlFlow};
|
||||
|
||||
use hir::{
|
||||
HasAttrs, Local, ModuleSource, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type,
|
||||
TypeInfo,
|
||||
HasAttrs, Local, ModuleSource, Name, PathResolution, ScopeDef, Semantics, SemanticsScope,
|
||||
Symbol, Type, TypeInfo,
|
||||
};
|
||||
use ide_db::{
|
||||
base_db::SourceDatabase, famous_defs::FamousDefs, helpers::is_editable_crate, FilePosition,
|
||||
|
@ -133,6 +133,7 @@ pub(crate) type ExistingDerives = FxHashSet<hir::Macro>;
|
|||
pub(crate) struct AttrCtx {
|
||||
pub(crate) kind: AttrKind,
|
||||
pub(crate) annotated_item_kind: Option<SyntaxKind>,
|
||||
pub(crate) derive_helpers: Vec<(Symbol, Symbol)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
|
@ -289,15 +290,14 @@ pub(crate) struct ParamContext {
|
|||
/// The state of the lifetime we are completing.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct LifetimeContext {
|
||||
pub(crate) lifetime: Option<ast::Lifetime>,
|
||||
pub(crate) kind: LifetimeKind,
|
||||
}
|
||||
|
||||
/// The kind of lifetime we are completing.
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum LifetimeKind {
|
||||
LifetimeParam { is_decl: bool, param: ast::LifetimeParam },
|
||||
Lifetime,
|
||||
LifetimeParam,
|
||||
Lifetime { in_lifetime_param_bound: bool, def: Option<hir::GenericDef> },
|
||||
LabelRef,
|
||||
LabelDef,
|
||||
}
|
||||
|
|
|
@ -562,7 +562,7 @@ fn expected_type_and_name(
|
|||
}
|
||||
|
||||
fn classify_lifetime(
|
||||
_sema: &Semantics<'_, RootDatabase>,
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
original_file: &SyntaxNode,
|
||||
lifetime: ast::Lifetime,
|
||||
) -> Option<LifetimeContext> {
|
||||
|
@ -571,21 +571,22 @@ fn classify_lifetime(
|
|||
return None;
|
||||
}
|
||||
|
||||
let lifetime =
|
||||
find_node_at_offset::<ast::Lifetime>(original_file, lifetime.syntax().text_range().start());
|
||||
let kind = match_ast! {
|
||||
match parent {
|
||||
ast::LifetimeParam(param) => LifetimeKind::LifetimeParam {
|
||||
is_decl: param.lifetime().as_ref() == Some(&lifetime),
|
||||
param
|
||||
},
|
||||
ast::LifetimeParam(_) => LifetimeKind::LifetimeParam,
|
||||
ast::BreakExpr(_) => LifetimeKind::LabelRef,
|
||||
ast::ContinueExpr(_) => LifetimeKind::LabelRef,
|
||||
ast::Label(_) => LifetimeKind::LabelDef,
|
||||
_ => LifetimeKind::Lifetime,
|
||||
_ => {
|
||||
let def = lifetime.as_ref().and_then(|lt| sema.scope(lt.syntax())?.generic_def());
|
||||
LifetimeKind::Lifetime { in_lifetime_param_bound: ast::TypeBound::can_cast(parent.kind()), def }
|
||||
},
|
||||
}
|
||||
};
|
||||
let lifetime = find_node_at_offset(original_file, lifetime.syntax().text_range().start());
|
||||
|
||||
Some(LifetimeContext { lifetime, kind })
|
||||
Some(LifetimeContext { kind })
|
||||
}
|
||||
|
||||
fn classify_name(
|
||||
|
@ -1129,7 +1130,22 @@ fn classify_name_ref(
|
|||
let is_trailing_outer_attr = kind != AttrKind::Inner
|
||||
&& non_trivia_sibling(attr.syntax().clone().into(), syntax::Direction::Next).is_none();
|
||||
let annotated_item_kind = if is_trailing_outer_attr { None } else { Some(attached.kind()) };
|
||||
Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind } })
|
||||
let derive_helpers = annotated_item_kind
|
||||
.filter(|kind| {
|
||||
matches!(
|
||||
kind,
|
||||
SyntaxKind::STRUCT
|
||||
| SyntaxKind::ENUM
|
||||
| SyntaxKind::UNION
|
||||
| SyntaxKind::VARIANT
|
||||
| SyntaxKind::TUPLE_FIELD
|
||||
| SyntaxKind::RECORD_FIELD
|
||||
)
|
||||
})
|
||||
.and_then(|_| nameref.as_ref()?.syntax().ancestors().find_map(ast::Adt::cast))
|
||||
.and_then(|adt| sema.derive_helpers_in_scope(&adt))
|
||||
.unwrap_or_default();
|
||||
Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind, derive_helpers } })
|
||||
};
|
||||
|
||||
// Infer the path kind
|
||||
|
|
|
@ -346,8 +346,7 @@ pub enum CompletionItemKind {
|
|||
impl_from!(SymbolKind for CompletionItemKind);
|
||||
|
||||
impl CompletionItemKind {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn tag(self) -> &'static str {
|
||||
pub fn tag(self) -> &'static str {
|
||||
match self {
|
||||
CompletionItemKind::SymbolKind(kind) => match kind {
|
||||
SymbolKind::Attribute => "at",
|
||||
|
|
|
@ -19,7 +19,7 @@ use ide_db::{
|
|||
},
|
||||
items_locator,
|
||||
syntax_helpers::tree_diff::diff,
|
||||
FilePosition, RootDatabase,
|
||||
FilePosition, FxHashSet, RootDatabase,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
@ -34,6 +34,7 @@ pub use crate::{
|
|||
config::{CallableSnippets, CompletionConfig},
|
||||
item::{
|
||||
CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch,
|
||||
CompletionRelevanceReturnType, CompletionRelevanceTypeMatch,
|
||||
},
|
||||
snippet::{Snippet, SnippetScope},
|
||||
};
|
||||
|
@ -50,6 +51,18 @@ pub struct CompletionFieldsToResolve {
|
|||
}
|
||||
|
||||
impl CompletionFieldsToResolve {
|
||||
pub fn from_client_capabilities(client_capability_fields: &FxHashSet<&str>) -> Self {
|
||||
Self {
|
||||
resolve_label_details: client_capability_fields.contains("labelDetails"),
|
||||
resolve_tags: client_capability_fields.contains("tags"),
|
||||
resolve_detail: client_capability_fields.contains("detail"),
|
||||
resolve_documentation: client_capability_fields.contains("documentation"),
|
||||
resolve_filter_text: client_capability_fields.contains("filterText"),
|
||||
resolve_text_edit: client_capability_fields.contains("textEdit"),
|
||||
resolve_command: client_capability_fields.contains("command"),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
resolve_label_details: false,
|
||||
|
|
|
@ -8,6 +8,70 @@ fn check(ra_fixture: &str, expect: Expect) {
|
|||
expect.assert_eq(&actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_helpers() {
|
||||
check(
|
||||
r#"
|
||||
//- /mac.rs crate:mac
|
||||
#![crate_type = "proc-macro"]
|
||||
|
||||
#[proc_macro_derive(MyDerive, attributes(my_cool_helper_attribute))]
|
||||
pub fn my_derive() {}
|
||||
|
||||
//- /lib.rs crate:lib deps:mac
|
||||
#[rustc_builtin_macro]
|
||||
pub macro derive($item:item) {}
|
||||
|
||||
#[derive(mac::MyDerive)]
|
||||
pub struct Foo(#[m$0] i32);
|
||||
"#,
|
||||
expect![[r#"
|
||||
at allow(…)
|
||||
at automatically_derived
|
||||
at cfg(…)
|
||||
at cfg_attr(…)
|
||||
at cold
|
||||
at deny(…)
|
||||
at deprecated
|
||||
at derive macro derive
|
||||
at derive(…)
|
||||
at doc = "…"
|
||||
at doc(alias = "…")
|
||||
at doc(hidden)
|
||||
at expect(…)
|
||||
at export_name = "…"
|
||||
at forbid(…)
|
||||
at global_allocator
|
||||
at ignore = "…"
|
||||
at inline
|
||||
at link
|
||||
at link_name = "…"
|
||||
at link_section = "…"
|
||||
at macro_export
|
||||
at macro_use
|
||||
at must_use
|
||||
at my_cool_helper_attribute derive helper of `MyDerive`
|
||||
at no_mangle
|
||||
at non_exhaustive
|
||||
at panic_handler
|
||||
at path = "…"
|
||||
at proc_macro
|
||||
at proc_macro_attribute
|
||||
at proc_macro_derive(…)
|
||||
at repr(…)
|
||||
at should_panic
|
||||
at target_feature(enable = "…")
|
||||
at test
|
||||
at track_caller
|
||||
at used
|
||||
at warn(…)
|
||||
md mac
|
||||
kw crate::
|
||||
kw self::
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proc_macros() {
|
||||
check(
|
||||
|
|
|
@ -733,6 +733,12 @@ impl NameRefClass {
|
|||
}
|
||||
None
|
||||
},
|
||||
ast::UseBoundGenericArgs(_) => {
|
||||
sema.resolve_use_type_arg(name_ref)
|
||||
.map(GenericParam::TypeParam)
|
||||
.map(Definition::GenericParam)
|
||||
.map(NameRefClass::Definition)
|
||||
},
|
||||
ast::ExternCrate(extern_crate_ast) => {
|
||||
let extern_crate = sema.to_def(&extern_crate_ast)?;
|
||||
let krate = extern_crate.resolved_crate(sema.db)?;
|
||||
|
@ -764,6 +770,7 @@ impl NameRefClass {
|
|||
sema.resolve_label(lifetime).map(Definition::Label).map(NameRefClass::Definition)
|
||||
}
|
||||
SyntaxKind::LIFETIME_ARG
|
||||
| SyntaxKind::USE_BOUND_GENERIC_ARGS
|
||||
| SyntaxKind::SELF_PARAM
|
||||
| SyntaxKind::TYPE_BOUND
|
||||
| SyntaxKind::WHERE_PRED
|
||||
|
@ -772,16 +779,6 @@ impl NameRefClass {
|
|||
.map(GenericParam::LifetimeParam)
|
||||
.map(Definition::GenericParam)
|
||||
.map(NameRefClass::Definition),
|
||||
// lifetime bounds, as in the 'b in 'a: 'b aren't wrapped in TypeBound nodes so we gotta check
|
||||
// if our lifetime is in a LifetimeParam without being the constrained lifetime
|
||||
_ if ast::LifetimeParam::cast(parent).and_then(|param| param.lifetime()).as_ref()
|
||||
!= Some(lifetime) =>
|
||||
{
|
||||
sema.resolve_lifetime_param(lifetime)
|
||||
.map(GenericParam::LifetimeParam)
|
||||
.map(Definition::GenericParam)
|
||||
.map(NameRefClass::Definition)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -286,7 +286,8 @@ impl Ctx<'_> {
|
|||
return None;
|
||||
}
|
||||
if path.segment().map_or(false, |s| {
|
||||
s.param_list().is_some() || (s.self_token().is_some() && path.parent_path().is_none())
|
||||
s.parenthesized_arg_list().is_some()
|
||||
|| (s.self_token().is_some() && path.parent_path().is_none())
|
||||
}) {
|
||||
// don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway
|
||||
// don't try to qualify sole `self` either, they are usually locals, but are returned as modules due to namespace clashing
|
||||
|
|
|
@ -29,7 +29,7 @@ const USELESS_NAME_PREFIXES: &[&str] = &["from_", "with_", "into_"];
|
|||
/// # Examples
|
||||
/// `Option<Name>` -> `Name`
|
||||
/// `Result<User, Error>` -> `User`
|
||||
const WRAPPER_TYPES: &[&str] = &["Box", "Option", "Result"];
|
||||
const WRAPPER_TYPES: &[&str] = &["Box", "Arc", "Rc", "Option", "Result"];
|
||||
|
||||
/// Prefixes to strip from methods names
|
||||
///
|
||||
|
@ -858,6 +858,32 @@ fn foo() { $0(bar())$0; }
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arc_value() {
|
||||
check(
|
||||
r#"
|
||||
struct Arc<T>(*const T);
|
||||
struct Seed;
|
||||
fn bar() -> Arc<Seed> {}
|
||||
fn foo() { $0(bar())$0; }
|
||||
"#,
|
||||
"seed",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rc_value() {
|
||||
check(
|
||||
r#"
|
||||
struct Rc<T>(*const T);
|
||||
struct Seed;
|
||||
fn bar() -> Rc<Seed> {}
|
||||
fn foo() { $0(bar())$0; }
|
||||
"#,
|
||||
"seed",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ref_call() {
|
||||
check(
|
||||
|
|
|
@ -37,4 +37,25 @@ fn foo() {
|
|||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_error_for_async_fn_traits() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
//- minicore: async_fn
|
||||
async fn f(it: impl AsyncFn(u32) -> i32) {
|
||||
let fut = it(0);
|
||||
let _: i32 = fut.await;
|
||||
}
|
||||
async fn g(mut it: impl AsyncFnMut(u32) -> i32) {
|
||||
let fut = it(0);
|
||||
let _: i32 = fut.await;
|
||||
}
|
||||
async fn h(it: impl AsyncFnOnce(u32) -> i32) {
|
||||
let fut = it(0);
|
||||
let _: i32 = fut.await;
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
442
crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs
Normal file
442
crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs
Normal file
|
@ -0,0 +1,442 @@
|
|||
use either::Either;
|
||||
use hir::GenericArgsProhibitedReason;
|
||||
use ide_db::assists::Assist;
|
||||
use ide_db::source_change::SourceChange;
|
||||
use ide_db::text_edit::TextEdit;
|
||||
use syntax::{ast, AstNode, TextRange};
|
||||
|
||||
use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
|
||||
|
||||
// Diagnostic: generic-args-prohibited
|
||||
//
|
||||
// This diagnostic is shown when generic arguments are provided for a type that does not accept
|
||||
// generic arguments.
|
||||
pub(crate) fn generic_args_prohibited(
|
||||
ctx: &DiagnosticsContext<'_>,
|
||||
d: &hir::GenericArgsProhibited,
|
||||
) -> Diagnostic {
|
||||
Diagnostic::new_with_syntax_node_ptr(
|
||||
ctx,
|
||||
DiagnosticCode::RustcHardError("E0109"),
|
||||
describe_reason(d.reason),
|
||||
d.args.map(Into::into),
|
||||
)
|
||||
.with_fixes(fixes(ctx, d))
|
||||
}
|
||||
|
||||
fn describe_reason(reason: GenericArgsProhibitedReason) -> String {
|
||||
let kind = match reason {
|
||||
GenericArgsProhibitedReason::Module => "modules",
|
||||
GenericArgsProhibitedReason::TyParam => "type parameters",
|
||||
GenericArgsProhibitedReason::SelfTy => "`Self`",
|
||||
GenericArgsProhibitedReason::PrimitiveTy => "builtin types",
|
||||
GenericArgsProhibitedReason::EnumVariant => {
|
||||
return "you can specify generic arguments on either the enum or the variant, but not both"
|
||||
.to_owned();
|
||||
}
|
||||
};
|
||||
format!("generic arguments are not allowed on {kind}")
|
||||
}
|
||||
|
||||
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::GenericArgsProhibited) -> Option<Vec<Assist>> {
|
||||
let file_id = d.args.file_id.file_id()?;
|
||||
let syntax = d.args.to_node(ctx.sema.db);
|
||||
let range = match &syntax {
|
||||
Either::Left(_) => syntax.syntax().text_range(),
|
||||
Either::Right(param_list) => {
|
||||
let path_segment = ast::PathSegment::cast(param_list.syntax().parent()?)?;
|
||||
let start = if let Some(coloncolon) = path_segment.coloncolon_token() {
|
||||
coloncolon.text_range().start()
|
||||
} else {
|
||||
param_list.syntax().text_range().start()
|
||||
};
|
||||
let end = if let Some(ret_type) = path_segment.ret_type() {
|
||||
ret_type.syntax().text_range().end()
|
||||
} else {
|
||||
param_list.syntax().text_range().end()
|
||||
};
|
||||
TextRange::new(start, end)
|
||||
}
|
||||
};
|
||||
Some(vec![fix(
|
||||
"remove_generic_args",
|
||||
"Remove these generics",
|
||||
SourceChange::from_text_edit(file_id, TextEdit::delete(range)),
|
||||
syntax.syntax().text_range(),
|
||||
)])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// This diagnostic was the first to be emitted in ty lowering, so the tests here also test
|
||||
// diagnostics in ty lowering in general (which is why there are so many of them).
|
||||
|
||||
use crate::tests::{check_diagnostics, check_fix};
|
||||
|
||||
#[test]
|
||||
fn primitives() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
//- /core.rs crate:core library
|
||||
#![rustc_coherence_is_core]
|
||||
impl str {
|
||||
pub fn trim() {}
|
||||
}
|
||||
|
||||
//- /lib.rs crate:foo deps:core
|
||||
fn bar<T>() {}
|
||||
|
||||
fn foo() {
|
||||
let _: (bool<()>, ());
|
||||
// ^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
let _ = <str<'_>>::trim;
|
||||
// ^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
bar::<u32<{ const { 1 + 1 } }>>();
|
||||
// ^^^^^^^^^^^^^^^^^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modules() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
pub mod foo {
|
||||
pub mod bar {
|
||||
pub struct Baz;
|
||||
|
||||
impl Baz {
|
||||
pub fn qux() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
let _: foo::<'_>::bar::Baz;
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on modules
|
||||
let _ = <foo::bar<()>::Baz>::qux;
|
||||
// ^^^^ 💡 error: generic arguments are not allowed on modules
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_parameters() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
fn foo<T, U>() {
|
||||
let _: T<'a>;
|
||||
// ^^^^ 💡 error: generic arguments are not allowed on type parameters
|
||||
let _: U::<{ 1 + 2 }>;
|
||||
// ^^^^^^^^^^^^^ 💡 error: generic arguments are not allowed on type parameters
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_like_generic_args() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
fn foo() {
|
||||
let _: bool(bool, i32) -> ();
|
||||
// ^^^^^^^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_signature() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
fn foo(
|
||||
_a: bool<'_>,
|
||||
// ^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
_b: i32::<i64>,
|
||||
// ^^^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
_c: &(&str<1>)
|
||||
// ^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
) -> ((), i32<bool>) {
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
((), 0)
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_static_type() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
const A: i32<bool> = 0;
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
static A: i32::<{ 1 + 3 }> = 0;
|
||||
// ^^^^^^^^^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix() {
|
||||
check_fix(
|
||||
r#"
|
||||
fn foo() {
|
||||
let _: bool<'_, (), { 1 + 1 }>$0;
|
||||
}"#,
|
||||
r#"
|
||||
fn foo() {
|
||||
let _: bool;
|
||||
}"#,
|
||||
);
|
||||
check_fix(
|
||||
r#"
|
||||
fn foo() {
|
||||
let _: bool::$0<'_, (), { 1 + 1 }>;
|
||||
}"#,
|
||||
r#"
|
||||
fn foo() {
|
||||
let _: bool;
|
||||
}"#,
|
||||
);
|
||||
check_fix(
|
||||
r#"
|
||||
fn foo() {
|
||||
let _: bool(i$032);
|
||||
}"#,
|
||||
r#"
|
||||
fn foo() {
|
||||
let _: bool;
|
||||
}"#,
|
||||
);
|
||||
check_fix(
|
||||
r#"
|
||||
fn foo() {
|
||||
let _: bool$0(i32) -> i64;
|
||||
}"#,
|
||||
r#"
|
||||
fn foo() {
|
||||
let _: bool;
|
||||
}"#,
|
||||
);
|
||||
check_fix(
|
||||
r#"
|
||||
fn foo() {
|
||||
let _: bool::(i$032) -> i64;
|
||||
}"#,
|
||||
r#"
|
||||
fn foo() {
|
||||
let _: bool;
|
||||
}"#,
|
||||
);
|
||||
check_fix(
|
||||
r#"
|
||||
fn foo() {
|
||||
let _: bool::(i32)$0;
|
||||
}"#,
|
||||
r#"
|
||||
fn foo() {
|
||||
let _: bool;
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn in_fields() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
struct A(bool<i32>);
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
struct B { v: bool<(), 1> }
|
||||
// ^^^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
union C {
|
||||
a: bool<i32>,
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
b: i32<bool>,
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
}
|
||||
enum D {
|
||||
A(bool<i32>),
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
B { v: i32<bool> },
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn in_generics() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
mod foo {
|
||||
pub trait Trait {}
|
||||
}
|
||||
|
||||
struct A<A: foo::<()>::Trait>(A)
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on modules
|
||||
where bool<i32>: foo::Trait;
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
union B<A: foo::<()>::Trait>
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on modules
|
||||
where bool<i32>: foo::Trait
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
{ a: A }
|
||||
enum C<A: foo::<()>::Trait>
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on modules
|
||||
where bool<i32>: foo::Trait
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
{}
|
||||
|
||||
fn f<A: foo::<()>::Trait>()
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on modules
|
||||
where bool<i32>: foo::Trait
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
{}
|
||||
|
||||
type D<A: foo::<()>::Trait> = A
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on modules
|
||||
where bool<i32>: foo::Trait;
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
|
||||
trait E<A: foo::<()>::Trait>
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on modules
|
||||
where bool<i32>: foo::Trait
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
{
|
||||
fn f<B: foo::<()>::Trait>()
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on modules
|
||||
where bool<i32>: foo::Trait
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
{}
|
||||
|
||||
type D<B: foo::<()>::Trait> = A
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on modules
|
||||
where bool<i32>: foo::Trait;
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
}
|
||||
|
||||
impl<A: foo::<()>::Trait> E for ()
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on modules
|
||||
where bool<i32>: foo::Trait
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
{
|
||||
fn f<B: foo::<()>::Trait>()
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on modules
|
||||
where bool<i32>: foo::Trait
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
{}
|
||||
|
||||
type D<B: foo::<()>::Trait> = A
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on modules
|
||||
where bool<i32>: foo::Trait;
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assoc_items() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
struct Foo;
|
||||
|
||||
trait Trait {
|
||||
fn f() -> bool<i32> { true }
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
type T = i32<bool>;
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
}
|
||||
|
||||
impl Trait for Foo {
|
||||
fn f() -> bool<i32> { true }
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
type T = i32<bool>;
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
}
|
||||
|
||||
impl Foo {
|
||||
fn f() -> bool<i32> { true }
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
type T = i32<bool>;
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_param_ty() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
fn foo<
|
||||
const A: bool<i32>,
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
B,
|
||||
C,
|
||||
const D: bool<i32>,
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
const E: bool<i32>,
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
>() {}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_defaults() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
struct Foo<A = bool<i32>>(A);
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn impl_self_ty() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
struct Foo<A>(A);
|
||||
trait Trait {}
|
||||
impl Foo<bool<i32>> {}
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
impl Trait for Foo<bool<i32>> {}
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn impl_trait() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
mod foo {
|
||||
pub trait Trait {}
|
||||
}
|
||||
impl foo::<()>::Trait for () {}
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on modules
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_alias() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
pub trait Trait {
|
||||
type Assoc;
|
||||
}
|
||||
type T = bool<i32>;
|
||||
// ^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
impl Trait for () {
|
||||
type Assoc = i32<bool>;
|
||||
// ^^^^^^ 💡 error: generic arguments are not allowed on builtin types
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1114,6 +1114,25 @@ fn test(x: Option<lib::PrivatelyUninhabited>) {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_exhaustive_may_be_empty() {
|
||||
check_diagnostics_no_bails(
|
||||
r"
|
||||
//- /main.rs crate:main deps:dep
|
||||
// In a different crate
|
||||
fn empty_match_on_empty_struct<T>(x: dep::UninhabitedStruct) -> T {
|
||||
match x {}
|
||||
}
|
||||
//- /dep.rs crate:dep
|
||||
#[non_exhaustive]
|
||||
pub struct UninhabitedStruct {
|
||||
pub never: !,
|
||||
// other fields
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
mod false_negatives {
|
||||
//! The implementation of match checking here is a work in progress. As we roll this out, we
|
||||
//! prefer false negatives to false positives (ideally there would be no false positives). This
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use hir::db::ExpandDatabase;
|
||||
use hir::HirFileIdExt;
|
||||
use hir::{HirFileIdExt, UnsafetyReason};
|
||||
use ide_db::text_edit::TextEdit;
|
||||
use ide_db::{assists::Assist, source_change::SourceChange};
|
||||
use syntax::{ast, SyntaxNode};
|
||||
|
@ -16,23 +16,35 @@ pub(crate) fn missing_unsafe(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsaf
|
|||
} else {
|
||||
DiagnosticCode::RustcHardError("E0133")
|
||||
};
|
||||
let operation = display_unsafety_reason(d.reason);
|
||||
Diagnostic::new_with_syntax_node_ptr(
|
||||
ctx,
|
||||
code,
|
||||
"this operation is unsafe and requires an unsafe function or block",
|
||||
d.expr.map(|it| it.into()),
|
||||
format!("{operation} is unsafe and requires an unsafe function or block"),
|
||||
d.node.map(|it| it.into()),
|
||||
)
|
||||
.with_fixes(fixes(ctx, d))
|
||||
}
|
||||
|
||||
fn display_unsafety_reason(reason: UnsafetyReason) -> &'static str {
|
||||
match reason {
|
||||
UnsafetyReason::UnionField => "access to union field",
|
||||
UnsafetyReason::UnsafeFnCall => "call to unsafe function",
|
||||
UnsafetyReason::InlineAsm => "use of inline assembly",
|
||||
UnsafetyReason::RawPtrDeref => "dereference of raw pointer",
|
||||
UnsafetyReason::MutableStatic => "use of mutable static",
|
||||
UnsafetyReason::ExternStatic => "use of extern static",
|
||||
}
|
||||
}
|
||||
|
||||
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Option<Vec<Assist>> {
|
||||
// The fixit will not work correctly for macro expansions, so we don't offer it in that case.
|
||||
if d.expr.file_id.is_macro() {
|
||||
if d.node.file_id.is_macro() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let root = ctx.sema.db.parse_or_expand(d.expr.file_id);
|
||||
let node = d.expr.value.to_node(&root);
|
||||
let root = ctx.sema.db.parse_or_expand(d.node.file_id);
|
||||
let node = d.node.value.to_node(&root);
|
||||
let expr = node.syntax().ancestors().find_map(ast::Expr::cast)?;
|
||||
|
||||
let node_to_add_unsafe_block = pick_best_node_to_add_unsafe_block(&expr)?;
|
||||
|
@ -40,7 +52,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Option<Vec<Ass
|
|||
let replacement = format!("unsafe {{ {} }}", node_to_add_unsafe_block.text());
|
||||
let edit = TextEdit::replace(node_to_add_unsafe_block.text_range(), replacement);
|
||||
let source_change =
|
||||
SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit);
|
||||
SourceChange::from_text_edit(d.node.file_id.original_file(ctx.sema.db), edit);
|
||||
Some(vec![fix("add_unsafe", "Add unsafe block", source_change, expr.syntax().text_range())])
|
||||
}
|
||||
|
||||
|
@ -110,7 +122,7 @@ fn main() {
|
|||
let x = &5_usize as *const usize;
|
||||
unsafe { let _y = *x; }
|
||||
let _z = *x;
|
||||
} //^^💡 error: this operation is unsafe and requires an unsafe function or block
|
||||
} //^^💡 error: dereference of raw pointer is unsafe and requires an unsafe function or block
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
@ -136,9 +148,9 @@ unsafe fn unsafe_fn() {
|
|||
|
||||
fn main() {
|
||||
unsafe_fn();
|
||||
//^^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block
|
||||
//^^^^^^^^^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block
|
||||
HasUnsafe.unsafe_fn();
|
||||
//^^^^^^^^^^^^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block
|
||||
//^^^^^^^^^^^^^^^^^^^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block
|
||||
unsafe {
|
||||
unsafe_fn();
|
||||
HasUnsafe.unsafe_fn();
|
||||
|
@ -162,7 +174,7 @@ static mut STATIC_MUT: Ty = Ty { a: 0 };
|
|||
|
||||
fn main() {
|
||||
let _x = STATIC_MUT.a;
|
||||
//^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block
|
||||
//^^^^^^^^^^💡 error: use of mutable static is unsafe and requires an unsafe function or block
|
||||
unsafe {
|
||||
let _x = STATIC_MUT.a;
|
||||
}
|
||||
|
@ -184,9 +196,9 @@ extern "C" {
|
|||
|
||||
fn main() {
|
||||
let _x = EXTERN;
|
||||
//^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block
|
||||
//^^^^^^💡 error: use of extern static is unsafe and requires an unsafe function or block
|
||||
let _x = EXTERN_MUT;
|
||||
//^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block
|
||||
//^^^^^^^^^^💡 error: use of mutable static is unsafe and requires an unsafe function or block
|
||||
unsafe {
|
||||
let _x = EXTERN;
|
||||
let _x = EXTERN_MUT;
|
||||
|
@ -234,7 +246,7 @@ extern "rust-intrinsic" {
|
|||
fn main() {
|
||||
let _ = bitreverse(12);
|
||||
let _ = floorf32(12.0);
|
||||
//^^^^^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block
|
||||
//^^^^^^^^^^^^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
@ -567,7 +579,7 @@ unsafe fn not_safe() -> u8 {
|
|||
fn main() {
|
||||
ed2021::safe();
|
||||
ed2024::not_safe();
|
||||
//^^^^^^^^^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block
|
||||
//^^^^^^^^^^^^^^^^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block
|
||||
}
|
||||
"#,
|
||||
)
|
||||
|
@ -591,7 +603,7 @@ unsafe fn foo(p: *mut i32) {
|
|||
#![warn(unsafe_op_in_unsafe_fn)]
|
||||
unsafe fn foo(p: *mut i32) {
|
||||
*p = 123;
|
||||
//^^💡 warn: this operation is unsafe and requires an unsafe function or block
|
||||
//^^💡 warn: dereference of raw pointer is unsafe and requires an unsafe function or block
|
||||
}
|
||||
"#,
|
||||
)
|
||||
|
@ -618,17 +630,119 @@ unsafe extern {
|
|||
fn main() {
|
||||
f();
|
||||
g();
|
||||
//^^^💡 error: this operation is unsafe and requires an unsafe function or block
|
||||
//^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block
|
||||
h();
|
||||
//^^^💡 error: this operation is unsafe and requires an unsafe function or block
|
||||
//^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block
|
||||
|
||||
let _ = S1;
|
||||
let _ = S2;
|
||||
//^^💡 error: this operation is unsafe and requires an unsafe function or block
|
||||
//^^💡 error: use of extern static is unsafe and requires an unsafe function or block
|
||||
let _ = S3;
|
||||
//^^💡 error: this operation is unsafe and requires an unsafe function or block
|
||||
//^^💡 error: use of extern static is unsafe and requires an unsafe function or block
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_unsafe_diagnostic_when_destructuring_union_with_wildcard() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
union Union { field: i32 }
|
||||
fn foo(v: &Union) {
|
||||
let Union { field: _ } = v;
|
||||
let Union { field: _ | _ } = v;
|
||||
Union { field: _ } = *v;
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_destructuring() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
union Union { field: u8 }
|
||||
fn foo(v @ Union { field: _field }: &Union) {
|
||||
// ^^^^^^ error: access to union field is unsafe and requires an unsafe function or block
|
||||
let Union { mut field } = v;
|
||||
// ^^^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block
|
||||
let Union { field: 0..=255 } = v;
|
||||
// ^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block
|
||||
let Union { field: 0
|
||||
// ^💡 error: access to union field is unsafe and requires an unsafe function or block
|
||||
| 1..=255 } = v;
|
||||
// ^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block
|
||||
Union { field } = *v;
|
||||
// ^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block
|
||||
match v {
|
||||
Union { field: _field } => {}
|
||||
// ^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block
|
||||
}
|
||||
if let Union { field: _field } = v {}
|
||||
// ^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block
|
||||
(|&Union { field }| { _ = field; })(v);
|
||||
// ^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_field_access() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
union Union { field: u8 }
|
||||
fn foo(v: &Union) {
|
||||
v.field;
|
||||
// ^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inline_asm() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
//- minicore: asm
|
||||
fn foo() {
|
||||
core::arch::asm!("");
|
||||
// ^^^^ error: use of inline assembly is unsafe and requires an unsafe function or block
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsafe_op_in_unsafe_fn_dismissed_in_signature() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
#![warn(unsafe_op_in_unsafe_fn)]
|
||||
union Union { field: u32 }
|
||||
unsafe fn foo(Union { field: _field }: Union) {}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_assignment_allowed() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
union Union { field: u32 }
|
||||
fn foo(mut v: Union) {
|
||||
v.field = 123;
|
||||
(v.field,) = (123,);
|
||||
*&mut v.field = 123;
|
||||
// ^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block
|
||||
}
|
||||
struct Struct { field: u32 }
|
||||
union Union2 { field: Struct }
|
||||
fn bar(mut v: Union2) {
|
||||
v.field.field = 123;
|
||||
}
|
||||
|
||||
"#,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
use either::Either;
|
||||
use hir::{db::ExpandDatabase, ClosureStyle, HirDisplay, HirFileIdExt, InFile, Type};
|
||||
use ide_db::text_edit::TextEdit;
|
||||
use ide_db::{famous_defs::FamousDefs, source_change::SourceChange};
|
||||
use hir::{db::ExpandDatabase, CallableKind, ClosureStyle, HirDisplay, HirFileIdExt, InFile, Type};
|
||||
use ide_db::{
|
||||
famous_defs::FamousDefs,
|
||||
source_change::{SourceChange, SourceChangeBuilder},
|
||||
text_edit::TextEdit,
|
||||
};
|
||||
use syntax::{
|
||||
ast::{
|
||||
self,
|
||||
edit::{AstNodeEdit, IndentLevel},
|
||||
BlockExpr, Expr, ExprStmt,
|
||||
syntax_factory::SyntaxFactory,
|
||||
BlockExpr, Expr, ExprStmt, HasArgList,
|
||||
},
|
||||
AstNode, AstPtr, TextSize,
|
||||
};
|
||||
|
@ -63,6 +67,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option<Vec<Assi
|
|||
let expr_ptr = &InFile { file_id: d.expr_or_pat.file_id, value: expr_ptr };
|
||||
add_reference(ctx, d, expr_ptr, &mut fixes);
|
||||
add_missing_ok_or_some(ctx, d, expr_ptr, &mut fixes);
|
||||
remove_unnecessary_wrapper(ctx, d, expr_ptr, &mut fixes);
|
||||
remove_semicolon(ctx, d, expr_ptr, &mut fixes);
|
||||
str_ref_to_owned(ctx, d, expr_ptr, &mut fixes);
|
||||
}
|
||||
|
@ -184,6 +189,89 @@ fn add_missing_ok_or_some(
|
|||
Some(())
|
||||
}
|
||||
|
||||
fn remove_unnecessary_wrapper(
|
||||
ctx: &DiagnosticsContext<'_>,
|
||||
d: &hir::TypeMismatch,
|
||||
expr_ptr: &InFile<AstPtr<ast::Expr>>,
|
||||
acc: &mut Vec<Assist>,
|
||||
) -> Option<()> {
|
||||
let db = ctx.sema.db;
|
||||
let root = db.parse_or_expand(expr_ptr.file_id);
|
||||
let expr = expr_ptr.value.to_node(&root);
|
||||
let expr = ctx.sema.original_ast_node(expr.clone())?;
|
||||
|
||||
let Expr::CallExpr(call_expr) = expr else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let callable = ctx.sema.resolve_expr_as_callable(&call_expr.expr()?)?;
|
||||
let CallableKind::TupleEnumVariant(variant) = callable.kind() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let actual_enum = d.actual.as_adt()?.as_enum()?;
|
||||
let famous_defs = FamousDefs(&ctx.sema, ctx.sema.scope(call_expr.syntax())?.krate());
|
||||
let core_option = famous_defs.core_option_Option();
|
||||
let core_result = famous_defs.core_result_Result();
|
||||
if Some(actual_enum) != core_option && Some(actual_enum) != core_result {
|
||||
return None;
|
||||
}
|
||||
|
||||
let inner_type = variant.fields(db).first()?.ty_with_args(db, d.actual.type_arguments());
|
||||
if !d.expected.could_unify_with(db, &inner_type) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let inner_arg = call_expr.arg_list()?.args().next()?;
|
||||
|
||||
let file_id = expr_ptr.file_id.original_file(db);
|
||||
let mut builder = SourceChangeBuilder::new(file_id);
|
||||
let mut editor;
|
||||
match inner_arg {
|
||||
// We're returning `()`
|
||||
Expr::TupleExpr(tup) if tup.fields().next().is_none() => {
|
||||
let parent = call_expr
|
||||
.syntax()
|
||||
.parent()
|
||||
.and_then(Either::<ast::ReturnExpr, ast::StmtList>::cast)?;
|
||||
|
||||
editor = builder.make_editor(parent.syntax());
|
||||
let make = SyntaxFactory::new();
|
||||
|
||||
match parent {
|
||||
Either::Left(ret_expr) => {
|
||||
editor.replace(ret_expr.syntax(), make.expr_return(None).syntax());
|
||||
}
|
||||
Either::Right(stmt_list) => {
|
||||
let new_block = if stmt_list.statements().next().is_none() {
|
||||
make.expr_empty_block()
|
||||
} else {
|
||||
make.block_expr(stmt_list.statements(), None)
|
||||
};
|
||||
|
||||
editor.replace(stmt_list.syntax().parent()?, new_block.syntax());
|
||||
}
|
||||
}
|
||||
|
||||
editor.add_mappings(make.finish_with_mappings());
|
||||
}
|
||||
_ => {
|
||||
editor = builder.make_editor(call_expr.syntax());
|
||||
editor.replace(call_expr.syntax(), inner_arg.syntax());
|
||||
}
|
||||
}
|
||||
|
||||
builder.add_file_edits(file_id, editor);
|
||||
let name = format!("Remove unnecessary {}() wrapper", variant.name(db).as_str());
|
||||
acc.push(fix(
|
||||
"remove_unnecessary_wrapper",
|
||||
&name,
|
||||
builder.finish(),
|
||||
call_expr.syntax().text_range(),
|
||||
));
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn remove_semicolon(
|
||||
ctx: &DiagnosticsContext<'_>,
|
||||
d: &hir::TypeMismatch,
|
||||
|
@ -243,7 +331,7 @@ fn str_ref_to_owned(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{
|
||||
check_diagnostics, check_diagnostics_with_disabled, check_fix, check_no_fix,
|
||||
check_diagnostics, check_diagnostics_with_disabled, check_fix, check_has_fix, check_no_fix,
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
@ -260,7 +348,7 @@ fn test(_arg: &i32) {}
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_reference_to_int() {
|
||||
fn add_reference_to_int() {
|
||||
check_fix(
|
||||
r#"
|
||||
fn main() {
|
||||
|
@ -278,7 +366,7 @@ fn test(_arg: &i32) {}
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_mutable_reference_to_int() {
|
||||
fn add_mutable_reference_to_int() {
|
||||
check_fix(
|
||||
r#"
|
||||
fn main() {
|
||||
|
@ -296,7 +384,7 @@ fn test(_arg: &mut i32) {}
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_reference_to_array() {
|
||||
fn add_reference_to_array() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: coerce_unsized
|
||||
|
@ -315,7 +403,7 @@ fn test(_arg: &[i32]) {}
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_reference_with_autoderef() {
|
||||
fn add_reference_with_autoderef() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: coerce_unsized, deref
|
||||
|
@ -348,7 +436,7 @@ fn test(_arg: &Bar) {}
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_reference_to_method_call() {
|
||||
fn add_reference_to_method_call() {
|
||||
check_fix(
|
||||
r#"
|
||||
fn main() {
|
||||
|
@ -372,7 +460,7 @@ impl Test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_reference_to_let_stmt() {
|
||||
fn add_reference_to_let_stmt() {
|
||||
check_fix(
|
||||
r#"
|
||||
fn main() {
|
||||
|
@ -388,7 +476,7 @@ fn main() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_reference_to_macro_call() {
|
||||
fn add_reference_to_macro_call() {
|
||||
check_fix(
|
||||
r#"
|
||||
macro_rules! thousand {
|
||||
|
@ -416,7 +504,7 @@ fn main() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_mutable_reference_to_let_stmt() {
|
||||
fn add_mutable_reference_to_let_stmt() {
|
||||
check_fix(
|
||||
r#"
|
||||
fn main() {
|
||||
|
@ -431,29 +519,6 @@ fn main() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_return_type_option() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
fn div(x: i32, y: i32) -> Option<i32> {
|
||||
if y == 0 {
|
||||
return None;
|
||||
}
|
||||
x / y$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn div(x: i32, y: i32) -> Option<i32> {
|
||||
if y == 0 {
|
||||
return None;
|
||||
}
|
||||
Some(x / y)
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_generic_type_mismatch() {
|
||||
check_diagnostics(
|
||||
|
@ -487,7 +552,53 @@ fn div(x: i32, y: i32) -> Option<i32> {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_return_type_option_tails() {
|
||||
fn wrap_return_type() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
fn div(x: i32, y: i32) -> Result<i32, ()> {
|
||||
if y == 0 {
|
||||
return Err(());
|
||||
}
|
||||
x / y$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn div(x: i32, y: i32) -> Result<i32, ()> {
|
||||
if y == 0 {
|
||||
return Err(());
|
||||
}
|
||||
Ok(x / y)
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrap_return_type_option() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
fn div(x: i32, y: i32) -> Option<i32> {
|
||||
if y == 0 {
|
||||
return None;
|
||||
}
|
||||
x / y$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn div(x: i32, y: i32) -> Option<i32> {
|
||||
if y == 0 {
|
||||
return None;
|
||||
}
|
||||
Some(x / y)
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrap_return_type_option_tails() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
|
@ -516,30 +627,7 @@ fn div(x: i32, y: i32) -> Option<i32> {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_return_type() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
fn div(x: i32, y: i32) -> Result<i32, ()> {
|
||||
if y == 0 {
|
||||
return Err(());
|
||||
}
|
||||
x / y$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn div(x: i32, y: i32) -> Result<i32, ()> {
|
||||
if y == 0 {
|
||||
return Err(());
|
||||
}
|
||||
Ok(x / y)
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_return_type_handles_generic_functions() {
|
||||
fn wrap_return_type_handles_generic_functions() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
|
@ -562,7 +650,7 @@ fn div<T>(x: T) -> Result<T, i32> {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_return_type_handles_type_aliases() {
|
||||
fn wrap_return_type_handles_type_aliases() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
|
@ -589,7 +677,7 @@ fn div(x: i32, y: i32) -> MyResult<i32> {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrapped_unit_as_block_tail_expr() {
|
||||
fn wrapped_unit_as_block_tail_expr() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: result
|
||||
|
@ -619,7 +707,7 @@ fn foo() -> Result<(), ()> {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrapped_unit_as_return_expr() {
|
||||
fn wrapped_unit_as_return_expr() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: result
|
||||
|
@ -642,7 +730,7 @@ fn foo(b: bool) -> Result<(), String> {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_in_const_and_static() {
|
||||
fn wrap_in_const_and_static() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
|
@ -664,7 +752,7 @@ const _: Option<()> = {Some(())};
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
|
||||
fn wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
|
||||
check_no_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
|
@ -674,7 +762,7 @@ fn foo() -> Result<(), i32> { 0$0 }
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() {
|
||||
fn wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() {
|
||||
check_no_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
|
@ -685,6 +773,254 @@ fn foo() -> SomeOtherEnum { 0$0 }
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwrap_return_type() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
fn div(x: i32, y: i32) -> i32 {
|
||||
if y == 0 {
|
||||
panic!();
|
||||
}
|
||||
Ok(x / y)$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn div(x: i32, y: i32) -> i32 {
|
||||
if y == 0 {
|
||||
panic!();
|
||||
}
|
||||
x / y
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwrap_return_type_option() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
fn div(x: i32, y: i32) -> i32 {
|
||||
if y == 0 {
|
||||
panic!();
|
||||
}
|
||||
Some(x / y)$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn div(x: i32, y: i32) -> i32 {
|
||||
if y == 0 {
|
||||
panic!();
|
||||
}
|
||||
x / y
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwrap_return_type_option_tails() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
fn div(x: i32, y: i32) -> i32 {
|
||||
if y == 0 {
|
||||
42
|
||||
} else if true {
|
||||
Some(100)$0
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn div(x: i32, y: i32) -> i32 {
|
||||
if y == 0 {
|
||||
42
|
||||
} else if true {
|
||||
100
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwrap_return_type_option_tail_unit() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
fn div(x: i32, y: i32) {
|
||||
if y == 0 {
|
||||
panic!();
|
||||
}
|
||||
|
||||
Ok(())$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn div(x: i32, y: i32) {
|
||||
if y == 0 {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwrap_return_type_handles_generic_functions() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
fn div<T>(x: T) -> T {
|
||||
if x == 0 {
|
||||
panic!();
|
||||
}
|
||||
$0Ok(x)
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn div<T>(x: T) -> T {
|
||||
if x == 0 {
|
||||
panic!();
|
||||
}
|
||||
x
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwrap_return_type_handles_type_aliases() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
type MyResult<T> = T;
|
||||
|
||||
fn div(x: i32, y: i32) -> MyResult<i32> {
|
||||
if y == 0 {
|
||||
panic!();
|
||||
}
|
||||
Ok(x $0/ y)
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
type MyResult<T> = T;
|
||||
|
||||
fn div(x: i32, y: i32) -> MyResult<i32> {
|
||||
if y == 0 {
|
||||
panic!();
|
||||
}
|
||||
x / y
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwrap_tail_expr() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: result
|
||||
fn foo() -> () {
|
||||
println!("Hello, world!");
|
||||
Ok(())$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn foo() -> () {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwrap_to_empty_block() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: result
|
||||
fn foo() -> () {
|
||||
Ok(())$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn foo() -> () {}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwrap_to_return_expr() {
|
||||
check_has_fix(
|
||||
r#"
|
||||
//- minicore: result
|
||||
fn foo(b: bool) -> () {
|
||||
if b {
|
||||
return $0Ok(());
|
||||
}
|
||||
|
||||
panic!("oh dear");
|
||||
}"#,
|
||||
r#"
|
||||
fn foo(b: bool) -> () {
|
||||
if b {
|
||||
return;
|
||||
}
|
||||
|
||||
panic!("oh dear");
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwrap_in_const_and_static() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
static A: () = {Some(($0))};
|
||||
"#,
|
||||
r#"
|
||||
static A: () = {};
|
||||
"#,
|
||||
);
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
const _: () = {Some(($0))};
|
||||
"#,
|
||||
r#"
|
||||
const _: () = {};
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwrap_return_type_not_applicable_when_inner_type_does_not_match_return_type() {
|
||||
check_no_fix(
|
||||
r#"
|
||||
//- minicore: result
|
||||
fn foo() -> i32 { $0Ok(()) }
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwrap_return_type_not_applicable_when_wrapper_type_is_not_result_or_option() {
|
||||
check_no_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
enum SomeOtherEnum { Ok(i32), Err(String) }
|
||||
|
||||
fn foo() -> i32 { SomeOtherEnum::Ok($042) }
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_semicolon() {
|
||||
check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#);
|
||||
|
|
|
@ -167,9 +167,9 @@ fn assoc_func_fix(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) -
|
|||
}
|
||||
|
||||
let method_name = call.name_ref()?;
|
||||
let assoc_func_call = format!("{receiver_type_adt_name}::{method_name}()");
|
||||
let assoc_func_path = format!("{receiver_type_adt_name}::{method_name}");
|
||||
|
||||
let assoc_func_call = make::expr_path(make::path_from_text(&assoc_func_call));
|
||||
let assoc_func_path = make::expr_path(make::path_from_text(&assoc_func_path));
|
||||
|
||||
let args: Vec<_> = if need_to_take_receiver_as_first_arg {
|
||||
std::iter::once(receiver).chain(call.arg_list()?.args()).collect()
|
||||
|
@ -178,7 +178,7 @@ fn assoc_func_fix(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) -
|
|||
};
|
||||
let args = make::arg_list(args);
|
||||
|
||||
let assoc_func_call_expr_string = make::expr_call(assoc_func_call, args).to_string();
|
||||
let assoc_func_call_expr_string = make::expr_call(assoc_func_path, args).to_string();
|
||||
|
||||
let file_id = ctx.sema.original_range_opt(call.receiver()?.syntax())?.file_id;
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ mod handlers {
|
|||
pub(crate) mod await_outside_of_async;
|
||||
pub(crate) mod break_outside_of_loop;
|
||||
pub(crate) mod expected_function;
|
||||
pub(crate) mod generic_args_prohibited;
|
||||
pub(crate) mod inactive_code;
|
||||
pub(crate) mod incoherent_impl;
|
||||
pub(crate) mod incorrect_case;
|
||||
|
@ -468,6 +469,7 @@ pub fn semantic_diagnostics(
|
|||
Some(it) => it,
|
||||
None => continue,
|
||||
},
|
||||
AnyDiagnostic::GenericArgsProhibited(d) => handlers::generic_args_prohibited::generic_args_prohibited(&ctx, &d)
|
||||
};
|
||||
res.push(d)
|
||||
}
|
||||
|
@ -542,7 +544,13 @@ fn handle_diag_from_macros(
|
|||
sema.db.lookup_intern_syntax_context(span.ctx).outer_expn.is_some_and(|expansion| {
|
||||
let macro_call =
|
||||
sema.db.lookup_intern_macro_call(expansion.as_macro_file().macro_call_id);
|
||||
// We don't want to show diagnostics for non-local macros at all, but proc macros authors
|
||||
// seem to rely on being able to emit non-warning-free code, so we don't want to show warnings
|
||||
// for them even when the proc macro comes from the same workspace (in rustc that's not a
|
||||
// problem because it doesn't have the concept of workspaces, and proc macros always reside
|
||||
// in a different crate).
|
||||
!Crate::from(macro_call.def.krate).origin(sema.db).is_local()
|
||||
|| !macro_call.def.kind.is_declarative()
|
||||
})
|
||||
}) {
|
||||
// Disable suggestions for external macros, they'll change library code and it's just bad.
|
||||
|
|
|
@ -359,8 +359,8 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
|
|||
)?;
|
||||
self.attempt_match_opt(
|
||||
phase,
|
||||
pattern_segment.param_list(),
|
||||
code_segment.param_list(),
|
||||
pattern_segment.parenthesized_arg_list(),
|
||||
code_segment.parenthesized_arg_list(),
|
||||
)?;
|
||||
}
|
||||
if matches!(phase, Phase::Second(_)) {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -8,8 +8,8 @@ use hir::{
|
|||
sym, ClosureStyle, HasVisibility, HirDisplay, HirDisplayError, HirWrite, ModuleDef,
|
||||
ModuleDefId, Semantics,
|
||||
};
|
||||
use ide_db::text_edit::TextEdit;
|
||||
use ide_db::{famous_defs::FamousDefs, FileRange, RootDatabase};
|
||||
use ide_db::{text_edit::TextEdit, FxHashSet};
|
||||
use itertools::Itertools;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use span::{Edition, EditionedFileId};
|
||||
|
@ -29,6 +29,7 @@ mod closing_brace;
|
|||
mod closure_captures;
|
||||
mod closure_ret;
|
||||
mod discriminant;
|
||||
mod extern_block;
|
||||
mod generic_param;
|
||||
mod implicit_drop;
|
||||
mod implicit_static;
|
||||
|
@ -116,6 +117,7 @@ pub(crate) fn inlay_hints(
|
|||
#[derive(Default)]
|
||||
struct InlayHintCtx {
|
||||
lifetime_stacks: Vec<Vec<SmolStr>>,
|
||||
extern_block_parent: Option<ast::ExternBlock>,
|
||||
}
|
||||
|
||||
pub(crate) fn inlay_hints_resolve(
|
||||
|
@ -174,12 +176,18 @@ fn handle_event(ctx: &mut InlayHintCtx, node: WalkEvent<SyntaxNode>) -> Option<S
|
|||
.unwrap_or_default();
|
||||
ctx.lifetime_stacks.push(params);
|
||||
}
|
||||
if let Some(node) = ast::ExternBlock::cast(node.clone()) {
|
||||
ctx.extern_block_parent = Some(node);
|
||||
}
|
||||
Some(node)
|
||||
}
|
||||
WalkEvent::Leave(n) => {
|
||||
if ast::AnyHasGenericParams::can_cast(n.kind()) {
|
||||
ctx.lifetime_stacks.pop();
|
||||
}
|
||||
if ast::ExternBlock::can_cast(n.kind()) {
|
||||
ctx.extern_block_parent = None;
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -234,12 +242,20 @@ fn hints(
|
|||
ast::Item(it) => match it {
|
||||
ast::Item::Fn(it) => {
|
||||
implicit_drop::hints(hints, famous_defs, config, file_id, &it);
|
||||
if let Some(extern_block) = &ctx.extern_block_parent {
|
||||
extern_block::fn_hints(hints, famous_defs, config, file_id, &it, extern_block);
|
||||
}
|
||||
lifetime::fn_hints(hints, ctx, famous_defs, config, file_id, it)
|
||||
},
|
||||
// static type elisions
|
||||
ast::Item::Static(it) => implicit_static::hints(hints, famous_defs, config, file_id, Either::Left(it)),
|
||||
ast::Item::Static(it) => {
|
||||
if let Some(extern_block) = &ctx.extern_block_parent {
|
||||
extern_block::static_hints(hints, famous_defs, config, file_id, &it, extern_block);
|
||||
}
|
||||
implicit_static::hints(hints, famous_defs, config, file_id, Either::Left(it))
|
||||
},
|
||||
ast::Item::Const(it) => implicit_static::hints(hints, famous_defs, config, file_id, Either::Right(it)),
|
||||
ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, file_id, it),
|
||||
ast::Item::ExternBlock(it) => extern_block::extern_block_hints(hints, famous_defs, config, file_id, it),
|
||||
_ => None,
|
||||
},
|
||||
// FIXME: trait object type elisions
|
||||
|
@ -289,6 +305,16 @@ pub struct InlayFieldsToResolve {
|
|||
}
|
||||
|
||||
impl InlayFieldsToResolve {
|
||||
pub fn from_client_capabilities(client_capability_fields: &FxHashSet<&str>) -> Self {
|
||||
Self {
|
||||
resolve_text_edits: client_capability_fields.contains("textEdits"),
|
||||
resolve_hint_tooltip: client_capability_fields.contains("tooltip"),
|
||||
resolve_label_tooltip: client_capability_fields.contains("label.tooltip"),
|
||||
resolve_label_location: client_capability_fields.contains("label.location"),
|
||||
resolve_label_command: client_capability_fields.contains("label.command"),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
resolve_text_edits: false,
|
||||
|
@ -358,6 +384,7 @@ pub enum InlayKind {
|
|||
Type,
|
||||
Drop,
|
||||
RangeExclusive,
|
||||
ExternUnsafety,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash)]
|
||||
|
|
138
crates/ide/src/inlay_hints/extern_block.rs
Normal file
138
crates/ide/src/inlay_hints/extern_block.rs
Normal file
|
@ -0,0 +1,138 @@
|
|||
//! Extern block hints
|
||||
use ide_db::{famous_defs::FamousDefs, text_edit::TextEdit};
|
||||
use span::EditionedFileId;
|
||||
use syntax::{ast, AstNode, SyntaxToken};
|
||||
|
||||
use crate::{InlayHint, InlayHintsConfig};
|
||||
|
||||
pub(super) fn extern_block_hints(
|
||||
acc: &mut Vec<InlayHint>,
|
||||
FamousDefs(_sema, _): &FamousDefs<'_, '_>,
|
||||
_config: &InlayHintsConfig,
|
||||
_file_id: EditionedFileId,
|
||||
extern_block: ast::ExternBlock,
|
||||
) -> Option<()> {
|
||||
if extern_block.unsafe_token().is_some() {
|
||||
return None;
|
||||
}
|
||||
let abi = extern_block.abi()?;
|
||||
acc.push(InlayHint {
|
||||
range: abi.syntax().text_range(),
|
||||
position: crate::InlayHintPosition::Before,
|
||||
pad_left: false,
|
||||
pad_right: true,
|
||||
kind: crate::InlayKind::ExternUnsafety,
|
||||
label: crate::InlayHintLabel::from("unsafe"),
|
||||
text_edit: Some(TextEdit::insert(abi.syntax().text_range().start(), "unsafe ".to_owned())),
|
||||
resolve_parent: Some(extern_block.syntax().text_range()),
|
||||
});
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub(super) fn fn_hints(
|
||||
acc: &mut Vec<InlayHint>,
|
||||
FamousDefs(_sema, _): &FamousDefs<'_, '_>,
|
||||
_config: &InlayHintsConfig,
|
||||
_file_id: EditionedFileId,
|
||||
fn_: &ast::Fn,
|
||||
extern_block: &ast::ExternBlock,
|
||||
) -> Option<()> {
|
||||
let implicit_unsafe = fn_.safe_token().is_none() && fn_.unsafe_token().is_none();
|
||||
if !implicit_unsafe {
|
||||
return None;
|
||||
}
|
||||
let fn_ = fn_.fn_token()?;
|
||||
acc.push(item_hint(extern_block, fn_));
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub(super) fn static_hints(
|
||||
acc: &mut Vec<InlayHint>,
|
||||
FamousDefs(_sema, _): &FamousDefs<'_, '_>,
|
||||
_config: &InlayHintsConfig,
|
||||
_file_id: EditionedFileId,
|
||||
static_: &ast::Static,
|
||||
extern_block: &ast::ExternBlock,
|
||||
) -> Option<()> {
|
||||
let implicit_unsafe = static_.safe_token().is_none() && static_.unsafe_token().is_none();
|
||||
if !implicit_unsafe {
|
||||
return None;
|
||||
}
|
||||
let static_ = static_.static_token()?;
|
||||
acc.push(item_hint(extern_block, static_));
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn item_hint(extern_block: &ast::ExternBlock, token: SyntaxToken) -> InlayHint {
|
||||
InlayHint {
|
||||
range: token.text_range(),
|
||||
position: crate::InlayHintPosition::Before,
|
||||
pad_left: false,
|
||||
pad_right: true,
|
||||
kind: crate::InlayKind::ExternUnsafety,
|
||||
label: crate::InlayHintLabel::from("unsafe"),
|
||||
text_edit: {
|
||||
let mut builder = TextEdit::builder();
|
||||
builder.insert(token.text_range().start(), "unsafe ".to_owned());
|
||||
if extern_block.unsafe_token().is_none() {
|
||||
if let Some(abi) = extern_block.abi() {
|
||||
builder.insert(abi.syntax().text_range().start(), "unsafe ".to_owned());
|
||||
}
|
||||
}
|
||||
Some(builder.finish())
|
||||
},
|
||||
resolve_parent: Some(extern_block.syntax().text_range()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::inlay_hints::tests::{check_with_config, DISABLED_CONFIG};
|
||||
|
||||
#[test]
|
||||
fn unadorned() {
|
||||
check_with_config(
|
||||
DISABLED_CONFIG,
|
||||
r#"
|
||||
extern "C" {
|
||||
//^^^^^^^^^^ unsafe
|
||||
static FOO: ();
|
||||
// ^^^^^^ unsafe
|
||||
pub static FOO: ();
|
||||
// ^^^^^^unsafe
|
||||
unsafe static FOO: ();
|
||||
safe static FOO: ();
|
||||
fn foo();
|
||||
// ^^ unsafe
|
||||
pub fn foo();
|
||||
// ^^ unsafe
|
||||
unsafe fn foo();
|
||||
safe fn foo();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adorned() {
|
||||
check_with_config(
|
||||
DISABLED_CONFIG,
|
||||
r#"
|
||||
unsafe extern "C" {
|
||||
static FOO: ();
|
||||
// ^^^^^^ unsafe
|
||||
pub static FOO: ();
|
||||
// ^^^^^^unsafe
|
||||
unsafe static FOO: ();
|
||||
safe static FOO: ();
|
||||
fn foo();
|
||||
// ^^ unsafe
|
||||
pub fn foo();
|
||||
// ^^ unsafe
|
||||
unsafe fn foo();
|
||||
safe fn foo();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -41,7 +41,15 @@ pub(super) fn fn_hints(
|
|||
fd,
|
||||
config,
|
||||
file_id,
|
||||
param_list,
|
||||
param_list.params().filter_map(|it| {
|
||||
Some((
|
||||
it.pat().and_then(|it| match it {
|
||||
ast::Pat::IdentPat(p) => p.name(),
|
||||
_ => None,
|
||||
}),
|
||||
it.ty()?,
|
||||
))
|
||||
}),
|
||||
generic_param_list,
|
||||
ret_type,
|
||||
self_param,
|
||||
|
@ -90,7 +98,15 @@ pub(super) fn fn_ptr_hints(
|
|||
fd,
|
||||
config,
|
||||
file_id,
|
||||
param_list,
|
||||
param_list.params().filter_map(|it| {
|
||||
Some((
|
||||
it.pat().and_then(|it| match it {
|
||||
ast::Pat::IdentPat(p) => p.name(),
|
||||
_ => None,
|
||||
}),
|
||||
it.ty()?,
|
||||
))
|
||||
}),
|
||||
generic_param_list,
|
||||
ret_type,
|
||||
None,
|
||||
|
@ -148,7 +164,7 @@ pub(super) fn fn_path_hints(
|
|||
fd,
|
||||
config,
|
||||
file_id,
|
||||
param_list,
|
||||
param_list.type_args().filter_map(|it| Some((None, it.ty()?))),
|
||||
generic_param_list,
|
||||
ret_type,
|
||||
None,
|
||||
|
@ -177,8 +193,8 @@ pub(super) fn fn_path_hints(
|
|||
)
|
||||
}
|
||||
|
||||
fn path_as_fn(path: &ast::Path) -> Option<(ast::ParamList, Option<ast::RetType>)> {
|
||||
path.segment().and_then(|it| it.param_list().zip(Some(it.ret_type())))
|
||||
fn path_as_fn(path: &ast::Path) -> Option<(ast::ParenthesizedArgList, Option<ast::RetType>)> {
|
||||
path.segment().and_then(|it| it.parenthesized_arg_list().zip(Some(it.ret_type())))
|
||||
}
|
||||
|
||||
fn hints_(
|
||||
|
@ -187,7 +203,7 @@ fn hints_(
|
|||
FamousDefs(_, _): &FamousDefs<'_, '_>,
|
||||
config: &InlayHintsConfig,
|
||||
_file_id: EditionedFileId,
|
||||
param_list: ast::ParamList,
|
||||
params: impl Iterator<Item = (Option<ast::Name>, ast::Type)>,
|
||||
generic_param_list: Option<ast::GenericParamList>,
|
||||
ret_type: Option<ast::RetType>,
|
||||
self_param: Option<ast::SelfParam>,
|
||||
|
@ -217,45 +233,34 @@ fn hints_(
|
|||
let is_elided = is_elided(&lifetime);
|
||||
acc.push((None, self_param.amp_token(), lifetime, is_elided));
|
||||
}
|
||||
param_list
|
||||
.params()
|
||||
.filter_map(|it| {
|
||||
Some((
|
||||
it.pat().and_then(|it| match it {
|
||||
ast::Pat::IdentPat(p) => p.name(),
|
||||
_ => None,
|
||||
}),
|
||||
it.ty()?,
|
||||
))
|
||||
})
|
||||
.for_each(|(name, ty)| {
|
||||
// FIXME: check path types
|
||||
walk_ty(&ty, &mut |ty| match ty {
|
||||
ast::Type::RefType(r) => {
|
||||
let lifetime = r.lifetime();
|
||||
let is_elided = is_elided(&lifetime);
|
||||
acc.push((name.clone(), r.amp_token(), lifetime, is_elided));
|
||||
false
|
||||
}
|
||||
ast::Type::FnPtrType(_) => {
|
||||
params.for_each(|(name, ty)| {
|
||||
// FIXME: check path types
|
||||
walk_ty(&ty, &mut |ty| match ty {
|
||||
ast::Type::RefType(r) => {
|
||||
let lifetime = r.lifetime();
|
||||
let is_elided = is_elided(&lifetime);
|
||||
acc.push((name.clone(), r.amp_token(), lifetime, is_elided));
|
||||
false
|
||||
}
|
||||
ast::Type::FnPtrType(_) => {
|
||||
is_trivial = false;
|
||||
true
|
||||
}
|
||||
ast::Type::PathType(t) => {
|
||||
if t.path()
|
||||
.and_then(|it| it.segment())
|
||||
.and_then(|it| it.parenthesized_arg_list())
|
||||
.is_some()
|
||||
{
|
||||
is_trivial = false;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
ast::Type::PathType(t) => {
|
||||
if t.path()
|
||||
.and_then(|it| it.segment())
|
||||
.and_then(|it| it.param_list())
|
||||
.is_some()
|
||||
{
|
||||
is_trivial = false;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
});
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
});
|
||||
acc
|
||||
};
|
||||
|
||||
|
@ -339,7 +344,10 @@ fn hints_(
|
|||
true
|
||||
}
|
||||
ast::Type::PathType(t) => {
|
||||
if t.path().and_then(|it| it.segment()).and_then(|it| it.param_list()).is_some()
|
||||
if t.path()
|
||||
.and_then(|it| it.segment())
|
||||
.and_then(|it| it.parenthesized_arg_list())
|
||||
.is_some()
|
||||
{
|
||||
is_trivial = false;
|
||||
true
|
||||
|
|
|
@ -402,6 +402,8 @@ impl Analysis {
|
|||
self.with_db(|db| typing::on_enter(db, position))
|
||||
}
|
||||
|
||||
pub const SUPPORTED_TRIGGER_CHARS: &'static str = typing::TRIGGER_CHARS;
|
||||
|
||||
/// Returns an edit which should be applied after a character was typed.
|
||||
///
|
||||
/// This is useful for some on-the-fly fixups, like adding `;` to `let =`
|
||||
|
@ -410,14 +412,16 @@ impl Analysis {
|
|||
&self,
|
||||
position: FilePosition,
|
||||
char_typed: char,
|
||||
autoclose: bool,
|
||||
chars_to_exclude: Option<String>,
|
||||
) -> Cancellable<Option<SourceChange>> {
|
||||
// Fast path to not even parse the file.
|
||||
if !typing::TRIGGER_CHARS.contains(char_typed) {
|
||||
return Ok(None);
|
||||
}
|
||||
if char_typed == '<' && !autoclose {
|
||||
return Ok(None);
|
||||
if let Some(chars_to_exclude) = chars_to_exclude {
|
||||
if chars_to_exclude.contains(char_typed) {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
self.with_db(|db| typing::on_char_typed(db, position, char_typed))
|
||||
|
|
|
@ -3102,6 +3102,75 @@ fn main() { let _: S; }
|
|||
r#"
|
||||
use lib::S as Baz;
|
||||
fn main() { let _: Baz; }
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_type_param_ref_in_use_bound() {
|
||||
check(
|
||||
"U",
|
||||
r#"
|
||||
fn foo<T>() -> impl use<T$0> Trait {}
|
||||
"#,
|
||||
r#"
|
||||
fn foo<U>() -> impl use<U> Trait {}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_type_param_in_use_bound() {
|
||||
check(
|
||||
"U",
|
||||
r#"
|
||||
fn foo<T$0>() -> impl use<T> Trait {}
|
||||
"#,
|
||||
r#"
|
||||
fn foo<U>() -> impl use<U> Trait {}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_lifetime_param_ref_in_use_bound() {
|
||||
check(
|
||||
"u",
|
||||
r#"
|
||||
fn foo<'t>() -> impl use<'t$0> Trait {}
|
||||
"#,
|
||||
r#"
|
||||
fn foo<'u>() -> impl use<'u> Trait {}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_lifetime_param_in_use_bound() {
|
||||
check(
|
||||
"u",
|
||||
r#"
|
||||
fn foo<'t$0>() -> impl use<'t> Trait {}
|
||||
"#,
|
||||
r#"
|
||||
fn foo<'u>() -> impl use<'u> Trait {}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_parent_type_param_in_use_bound() {
|
||||
check(
|
||||
"U",
|
||||
r#"
|
||||
trait Trait<T> {
|
||||
fn foo() -> impl use<T$0> Trait {}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
trait Trait<U> {
|
||||
fn foo() -> impl use<U> Trait {}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -151,6 +151,14 @@ fn punctuation(
|
|||
T!['['] | T![']'] => HlPunct::Bracket,
|
||||
T!['{'] | T!['}'] => HlPunct::Brace,
|
||||
T!['('] | T![')'] => HlPunct::Parenthesis,
|
||||
T![>]
|
||||
if parent
|
||||
.as_ref()
|
||||
.and_then(SyntaxNode::parent)
|
||||
.map_or(false, |it| it.kind() == MACRO_RULES) =>
|
||||
{
|
||||
return HlOperator::Other.into()
|
||||
}
|
||||
T![<] | T![>] => HlPunct::Angle,
|
||||
T![,] => HlPunct::Comma,
|
||||
T![:] => HlPunct::Colon,
|
||||
|
|
|
@ -46,7 +46,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
|||
.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
|
||||
</style>
|
||||
<pre><code><span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">foo</span> <span class="brace">{</span>
|
||||
<span class="parenthesis">(</span><span class="punctuation">$</span>foo<span class="colon">:</span>ident<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span>
|
||||
<span class="parenthesis">(</span><span class="punctuation">$</span>foo<span class="colon">:</span>ident<span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span>
|
||||
<span class="keyword">mod</span> y <span class="brace">{</span>
|
||||
<span class="keyword">pub</span> <span class="keyword">struct</span> <span class="punctuation">$</span>foo<span class="semicolon">;</span>
|
||||
<span class="brace">}</span>
|
||||
|
|
|
@ -46,7 +46,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
|||
.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
|
||||
</style>
|
||||
<pre><code><span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">id</span> <span class="brace">{</span>
|
||||
<span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span>
|
||||
<span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span>
|
||||
<span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span>
|
||||
<span class="brace">}</span><span class="semicolon">;</span>
|
||||
<span class="brace">}</span>
|
||||
|
@ -79,7 +79,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
|||
<span class="brace">}</span>
|
||||
|
||||
<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">unsafe_deref</span> <span class="brace">{</span>
|
||||
<span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span>
|
||||
<span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span>
|
||||
<span class="punctuation">*</span><span class="parenthesis">(</span><span class="punctuation">&</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="keyword">as</span> <span class="punctuation">*</span><span class="keyword">const</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="parenthesis">)</span>
|
||||
<span class="brace">}</span><span class="semicolon">;</span>
|
||||
<span class="brace">}</span></code></pre>
|
|
@ -147,11 +147,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
|||
<span class="brace">}</span>
|
||||
|
||||
<span class="comment documentation">/// ```</span>
|
||||
<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">macro_rules</span><span class="macro_bang injected">!</span><span class="none injected"> </span><span class="macro declaration injected">noop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected"> </span><span class="parenthesis injected">(</span><span class="punctuation injected">$</span><span class="none injected">expr</span><span class="colon injected">:</span><span class="none injected">expr</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="operator injected">=</span><span class="angle injected">></span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected"> </span><span class="punctuation injected">$</span><span class="none injected">expr </span><span class="brace injected">}</span><span class="brace injected">}</span>
|
||||
<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">macro_rules</span><span class="macro_bang injected">!</span><span class="none injected"> </span><span class="macro declaration injected">noop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected"> </span><span class="parenthesis injected">(</span><span class="punctuation injected">$</span><span class="none injected">expr</span><span class="colon injected">:</span><span class="none injected">expr</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="operator injected">=</span><span class="operator injected">></span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected"> </span><span class="punctuation injected">$</span><span class="none injected">expr </span><span class="brace injected">}</span><span class="brace injected">}</span>
|
||||
<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="macro injected">noop</span><span class="macro_bang injected">!</span><span class="parenthesis injected macro">(</span><span class="numeric_literal injected macro">1</span><span class="parenthesis injected macro">)</span><span class="semicolon injected">;</span>
|
||||
<span class="comment documentation">/// ```</span>
|
||||
<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">noop</span> <span class="brace">{</span>
|
||||
<span class="parenthesis">(</span><span class="punctuation">$</span>expr<span class="colon">:</span>expr<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span>
|
||||
<span class="parenthesis">(</span><span class="punctuation">$</span>expr<span class="colon">:</span>expr<span class="parenthesis">)</span> <span class="operator">=</span><span class="operator">></span> <span class="brace">{</span>
|
||||
<span class="punctuation">$</span>expr
|
||||
<span class="brace">}</span>
|
||||
<span class="brace">}</span>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue