diff options
46 files changed, 2030 insertions, 1699 deletions
diff --git a/Cargo.lock b/Cargo.lock index c06236692..007f05b4d 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -114,7 +114,7 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" | |||
114 | [[package]] | 114 | [[package]] |
115 | name = "chalk-derive" | 115 | name = "chalk-derive" |
116 | version = "0.10.1-dev" | 116 | version = "0.10.1-dev" |
117 | source = "git+https://github.com/rust-lang/chalk.git?rev=3e9c2503ae9c5277c2acb74624dc267876dd89b3#3e9c2503ae9c5277c2acb74624dc267876dd89b3" | 117 | source = "git+https://github.com/rust-lang/chalk.git?rev=eaab84b394007d1bed15f5470409a6ea02900a96#eaab84b394007d1bed15f5470409a6ea02900a96" |
118 | dependencies = [ | 118 | dependencies = [ |
119 | "proc-macro2", | 119 | "proc-macro2", |
120 | "quote", | 120 | "quote", |
@@ -125,7 +125,7 @@ dependencies = [ | |||
125 | [[package]] | 125 | [[package]] |
126 | name = "chalk-engine" | 126 | name = "chalk-engine" |
127 | version = "0.10.1-dev" | 127 | version = "0.10.1-dev" |
128 | source = "git+https://github.com/rust-lang/chalk.git?rev=3e9c2503ae9c5277c2acb74624dc267876dd89b3#3e9c2503ae9c5277c2acb74624dc267876dd89b3" | 128 | source = "git+https://github.com/rust-lang/chalk.git?rev=eaab84b394007d1bed15f5470409a6ea02900a96#eaab84b394007d1bed15f5470409a6ea02900a96" |
129 | dependencies = [ | 129 | dependencies = [ |
130 | "chalk-macros", | 130 | "chalk-macros", |
131 | "rustc-hash", | 131 | "rustc-hash", |
@@ -134,7 +134,7 @@ dependencies = [ | |||
134 | [[package]] | 134 | [[package]] |
135 | name = "chalk-ir" | 135 | name = "chalk-ir" |
136 | version = "0.10.1-dev" | 136 | version = "0.10.1-dev" |
137 | source = "git+https://github.com/rust-lang/chalk.git?rev=3e9c2503ae9c5277c2acb74624dc267876dd89b3#3e9c2503ae9c5277c2acb74624dc267876dd89b3" | 137 | source = "git+https://github.com/rust-lang/chalk.git?rev=eaab84b394007d1bed15f5470409a6ea02900a96#eaab84b394007d1bed15f5470409a6ea02900a96" |
138 | dependencies = [ | 138 | dependencies = [ |
139 | "chalk-derive", | 139 | "chalk-derive", |
140 | "chalk-engine", | 140 | "chalk-engine", |
@@ -144,7 +144,7 @@ dependencies = [ | |||
144 | [[package]] | 144 | [[package]] |
145 | name = "chalk-macros" | 145 | name = "chalk-macros" |
146 | version = "0.10.1-dev" | 146 | version = "0.10.1-dev" |
147 | source = "git+https://github.com/rust-lang/chalk.git?rev=3e9c2503ae9c5277c2acb74624dc267876dd89b3#3e9c2503ae9c5277c2acb74624dc267876dd89b3" | 147 | source = "git+https://github.com/rust-lang/chalk.git?rev=eaab84b394007d1bed15f5470409a6ea02900a96#eaab84b394007d1bed15f5470409a6ea02900a96" |
148 | dependencies = [ | 148 | dependencies = [ |
149 | "lazy_static", | 149 | "lazy_static", |
150 | ] | 150 | ] |
@@ -152,7 +152,7 @@ dependencies = [ | |||
152 | [[package]] | 152 | [[package]] |
153 | name = "chalk-rust-ir" | 153 | name = "chalk-rust-ir" |
154 | version = "0.10.1-dev" | 154 | version = "0.10.1-dev" |
155 | source = "git+https://github.com/rust-lang/chalk.git?rev=3e9c2503ae9c5277c2acb74624dc267876dd89b3#3e9c2503ae9c5277c2acb74624dc267876dd89b3" | 155 | source = "git+https://github.com/rust-lang/chalk.git?rev=eaab84b394007d1bed15f5470409a6ea02900a96#eaab84b394007d1bed15f5470409a6ea02900a96" |
156 | dependencies = [ | 156 | dependencies = [ |
157 | "chalk-derive", | 157 | "chalk-derive", |
158 | "chalk-engine", | 158 | "chalk-engine", |
@@ -163,14 +163,14 @@ dependencies = [ | |||
163 | [[package]] | 163 | [[package]] |
164 | name = "chalk-solve" | 164 | name = "chalk-solve" |
165 | version = "0.10.1-dev" | 165 | version = "0.10.1-dev" |
166 | source = "git+https://github.com/rust-lang/chalk.git?rev=3e9c2503ae9c5277c2acb74624dc267876dd89b3#3e9c2503ae9c5277c2acb74624dc267876dd89b3" | 166 | source = "git+https://github.com/rust-lang/chalk.git?rev=eaab84b394007d1bed15f5470409a6ea02900a96#eaab84b394007d1bed15f5470409a6ea02900a96" |
167 | dependencies = [ | 167 | dependencies = [ |
168 | "chalk-derive", | 168 | "chalk-derive", |
169 | "chalk-engine", | 169 | "chalk-engine", |
170 | "chalk-ir", | 170 | "chalk-ir", |
171 | "chalk-macros", | 171 | "chalk-macros", |
172 | "chalk-rust-ir", | 172 | "chalk-rust-ir", |
173 | "ena 0.13.1", | 173 | "ena", |
174 | "itertools", | 174 | "itertools", |
175 | "petgraph", | 175 | "petgraph", |
176 | "rustc-hash", | 176 | "rustc-hash", |
@@ -309,15 +309,6 @@ checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" | |||
309 | 309 | ||
310 | [[package]] | 310 | [[package]] |
311 | name = "ena" | 311 | name = "ena" |
312 | version = "0.13.1" | ||
313 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
314 | checksum = "8944dc8fa28ce4a38f778bd46bf7d923fe73eed5a439398507246c8e017e6f36" | ||
315 | dependencies = [ | ||
316 | "log", | ||
317 | ] | ||
318 | |||
319 | [[package]] | ||
320 | name = "ena" | ||
321 | version = "0.14.0" | 312 | version = "0.14.0" |
322 | source = "registry+https://github.com/rust-lang/crates.io-index" | 313 | source = "registry+https://github.com/rust-lang/crates.io-index" |
323 | checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" | 314 | checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" |
@@ -354,9 +345,9 @@ dependencies = [ | |||
354 | 345 | ||
355 | [[package]] | 346 | [[package]] |
356 | name = "fixedbitset" | 347 | name = "fixedbitset" |
357 | version = "0.1.9" | 348 | version = "0.2.0" |
358 | source = "registry+https://github.com/rust-lang/crates.io-index" | 349 | source = "registry+https://github.com/rust-lang/crates.io-index" |
359 | checksum = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" | 350 | checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" |
360 | 351 | ||
361 | [[package]] | 352 | [[package]] |
362 | name = "fnv" | 353 | name = "fnv" |
@@ -658,9 +649,9 @@ dependencies = [ | |||
658 | 649 | ||
659 | [[package]] | 650 | [[package]] |
660 | name = "lsp-server" | 651 | name = "lsp-server" |
661 | version = "0.3.1" | 652 | version = "0.3.2" |
662 | source = "registry+https://github.com/rust-lang/crates.io-index" | 653 | source = "registry+https://github.com/rust-lang/crates.io-index" |
663 | checksum = "5383e043329615624bbf45e1ba27bd75c176762b2592855c659bc28ac580a06b" | 654 | checksum = "dccec31bfd027ac0dd288a78e19005fd89624d9099456e284b5241316a6c3072" |
664 | dependencies = [ | 655 | dependencies = [ |
665 | "crossbeam-channel", | 656 | "crossbeam-channel", |
666 | "log", | 657 | "log", |
@@ -814,12 +805,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
814 | checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" | 805 | checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" |
815 | 806 | ||
816 | [[package]] | 807 | [[package]] |
817 | name = "ordermap" | ||
818 | version = "0.3.5" | ||
819 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
820 | checksum = "a86ed3f5f244b372d6b1a00b72ef7f8876d0bc6a78a4c9985c53614041512063" | ||
821 | |||
822 | [[package]] | ||
823 | name = "parking_lot" | 808 | name = "parking_lot" |
824 | version = "0.10.2" | 809 | version = "0.10.2" |
825 | source = "registry+https://github.com/rust-lang/crates.io-index" | 810 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -873,12 +858,12 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" | |||
873 | 858 | ||
874 | [[package]] | 859 | [[package]] |
875 | name = "petgraph" | 860 | name = "petgraph" |
876 | version = "0.4.13" | 861 | version = "0.5.0" |
877 | source = "registry+https://github.com/rust-lang/crates.io-index" | 862 | source = "registry+https://github.com/rust-lang/crates.io-index" |
878 | checksum = "9c3659d1ee90221741f65dd128d9998311b0e40c5d3c23a62445938214abce4f" | 863 | checksum = "29c127eea4a29ec6c85d153c59dc1213f33ec74cead30fe4730aecc88cc1fd92" |
879 | dependencies = [ | 864 | dependencies = [ |
880 | "fixedbitset", | 865 | "fixedbitset", |
881 | "ordermap", | 866 | "indexmap", |
882 | ] | 867 | ] |
883 | 868 | ||
884 | [[package]] | 869 | [[package]] |
@@ -1053,7 +1038,7 @@ dependencies = [ | |||
1053 | "chalk-ir", | 1038 | "chalk-ir", |
1054 | "chalk-rust-ir", | 1039 | "chalk-rust-ir", |
1055 | "chalk-solve", | 1040 | "chalk-solve", |
1056 | "ena 0.14.0", | 1041 | "ena", |
1057 | "insta", | 1042 | "insta", |
1058 | "itertools", | 1043 | "itertools", |
1059 | "log", | 1044 | "log", |
diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs index f3af70a3e..5b1a4680b 100644 --- a/crates/ra_assists/src/assist_context.rs +++ b/crates/ra_assists/src/assist_context.rs | |||
@@ -5,7 +5,7 @@ use hir::Semantics; | |||
5 | use ra_db::{FileId, FileRange}; | 5 | use ra_db::{FileId, FileRange}; |
6 | use ra_fmt::{leading_indent, reindent}; | 6 | use ra_fmt::{leading_indent, reindent}; |
7 | use ra_ide_db::{ | 7 | use ra_ide_db::{ |
8 | source_change::{SingleFileChange, SourceChange}, | 8 | source_change::{SourceChange, SourceFileEdit}, |
9 | RootDatabase, | 9 | RootDatabase, |
10 | }; | 10 | }; |
11 | use ra_syntax::{ | 11 | use ra_syntax::{ |
@@ -150,11 +150,10 @@ impl Assists { | |||
150 | self.add_impl(label, f) | 150 | self.add_impl(label, f) |
151 | } | 151 | } |
152 | fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { | 152 | fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { |
153 | let change_label = label.label.clone(); | ||
154 | let source_change = if self.resolve { | 153 | let source_change = if self.resolve { |
155 | let mut builder = AssistBuilder::new(self.file); | 154 | let mut builder = AssistBuilder::new(self.file); |
156 | f(&mut builder); | 155 | f(&mut builder); |
157 | Some(builder.finish(change_label)) | 156 | Some(builder.finish()) |
158 | } else { | 157 | } else { |
159 | None | 158 | None |
160 | }; | 159 | }; |
@@ -246,9 +245,10 @@ impl AssistBuilder { | |||
246 | &mut self.edit | 245 | &mut self.edit |
247 | } | 246 | } |
248 | 247 | ||
249 | fn finish(self, change_label: String) -> SourceChange { | 248 | fn finish(self) -> SourceChange { |
250 | let edit = self.edit.finish(); | 249 | let edit = self.edit.finish(); |
251 | let mut res = SingleFileChange { label: change_label, edit }.into_source_change(self.file); | 250 | let source_file_edit = SourceFileEdit { file_id: self.file, edit }; |
251 | let mut res: SourceChange = source_file_edit.into(); | ||
252 | if self.is_snippet { | 252 | if self.is_snippet { |
253 | res.is_snippet = true; | 253 | res.is_snippet = true; |
254 | } | 254 | } |
diff --git a/crates/ra_assists/src/handlers/reorder_fields.rs b/crates/ra_assists/src/handlers/reorder_fields.rs index 30229edc2..897da2832 100644 --- a/crates/ra_assists/src/handlers/reorder_fields.rs +++ b/crates/ra_assists/src/handlers/reorder_fields.rs | |||
@@ -23,7 +23,7 @@ use crate::{AssistContext, AssistId, Assists}; | |||
23 | // ``` | 23 | // ``` |
24 | // | 24 | // |
25 | pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 25 | pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
26 | reorder::<ast::RecordLit>(acc, ctx.clone()).or_else(|| reorder::<ast::RecordPat>(acc, ctx)) | 26 | reorder::<ast::RecordLit>(acc, ctx).or_else(|| reorder::<ast::RecordPat>(acc, ctx)) |
27 | } | 27 | } |
28 | 28 | ||
29 | fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 29 | fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs index 373a7f7cc..62dd3547f 100644 --- a/crates/ra_assists/src/tests.rs +++ b/crates/ra_assists/src/tests.rs | |||
@@ -7,8 +7,7 @@ use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; | |||
7 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; | 7 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; |
8 | use ra_syntax::TextRange; | 8 | use ra_syntax::TextRange; |
9 | use test_utils::{ | 9 | use test_utils::{ |
10 | add_cursor, assert_eq_text, extract_offset, extract_range, extract_range_or_offset, | 10 | assert_eq_text, extract_offset, extract_range, extract_range_or_offset, RangeOrOffset, |
11 | RangeOrOffset, | ||
12 | }; | 11 | }; |
13 | 12 | ||
14 | use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; | 13 | use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; |
@@ -103,12 +102,6 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult) { | |||
103 | 102 | ||
104 | let mut actual = db.file_text(change.file_id).as_ref().to_owned(); | 103 | let mut actual = db.file_text(change.file_id).as_ref().to_owned(); |
105 | change.edit.apply(&mut actual); | 104 | change.edit.apply(&mut actual); |
106 | |||
107 | if !source_change.is_snippet { | ||
108 | if let Some(off) = source_change.cursor_position { | ||
109 | actual = add_cursor(&actual, off.offset) | ||
110 | } | ||
111 | } | ||
112 | assert_eq_text!(after, &actual); | 105 | assert_eq_text!(after, &actual); |
113 | } | 106 | } |
114 | (Some(assist), ExpectedResult::Target(target)) => { | 107 | (Some(assist), ExpectedResult::Target(target)) => { |
diff --git a/crates/ra_hir_def/src/lang_item.rs b/crates/ra_hir_def/src/lang_item.rs index d96ac8c0a..d962db3cc 100644 --- a/crates/ra_hir_def/src/lang_item.rs +++ b/crates/ra_hir_def/src/lang_item.rs | |||
@@ -73,8 +73,8 @@ pub struct LangItems { | |||
73 | } | 73 | } |
74 | 74 | ||
75 | impl LangItems { | 75 | impl LangItems { |
76 | pub fn target<'a>(&'a self, item: &str) -> Option<&'a LangItemTarget> { | 76 | pub fn target(&self, item: &str) -> Option<LangItemTarget> { |
77 | self.items.get(item) | 77 | self.items.get(item).copied() |
78 | } | 78 | } |
79 | 79 | ||
80 | /// Salsa query. This will look for lang items in a specific crate. | 80 | /// Salsa query. This will look for lang items in a specific crate. |
@@ -163,9 +163,13 @@ impl LangItems { | |||
163 | ) where | 163 | ) where |
164 | T: Into<AttrDefId> + Copy, | 164 | T: Into<AttrDefId> + Copy, |
165 | { | 165 | { |
166 | let attrs = db.attrs(item.into()); | 166 | if let Some(lang_item_name) = lang_attr(db, item) { |
167 | if let Some(lang_item_name) = attrs.by_key("lang").string_value() { | ||
168 | self.items.entry(lang_item_name.clone()).or_insert_with(|| constructor(item)); | 167 | self.items.entry(lang_item_name.clone()).or_insert_with(|| constructor(item)); |
169 | } | 168 | } |
170 | } | 169 | } |
171 | } | 170 | } |
171 | |||
172 | pub fn lang_attr(db: &dyn DefDatabase, item: impl Into<AttrDefId> + Copy) -> Option<SmolStr> { | ||
173 | let attrs = db.attrs(item.into()); | ||
174 | attrs.by_key("lang").string_value().cloned() | ||
175 | } | ||
diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml index 5fc0ec5e3..b2de7fa34 100644 --- a/crates/ra_hir_ty/Cargo.toml +++ b/crates/ra_hir_ty/Cargo.toml | |||
@@ -27,9 +27,9 @@ test_utils = { path = "../test_utils" } | |||
27 | 27 | ||
28 | scoped-tls = "1" | 28 | scoped-tls = "1" |
29 | 29 | ||
30 | chalk-solve = { git = "https://github.com/rust-lang/chalk.git", rev = "3e9c2503ae9c5277c2acb74624dc267876dd89b3" } | 30 | chalk-solve = { git = "https://github.com/rust-lang/chalk.git", rev = "eaab84b394007d1bed15f5470409a6ea02900a96" } |
31 | chalk-rust-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "3e9c2503ae9c5277c2acb74624dc267876dd89b3" } | 31 | chalk-rust-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "eaab84b394007d1bed15f5470409a6ea02900a96" } |
32 | chalk-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "3e9c2503ae9c5277c2acb74624dc267876dd89b3" } | 32 | chalk-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "eaab84b394007d1bed15f5470409a6ea02900a96" } |
33 | 33 | ||
34 | [dev-dependencies] | 34 | [dev-dependencies] |
35 | insta = "0.16.0" | 35 | insta = "0.16.0" |
diff --git a/crates/ra_hir_ty/src/db.rs b/crates/ra_hir_ty/src/db.rs index fdb49560b..0a8bb24ac 100644 --- a/crates/ra_hir_ty/src/db.rs +++ b/crates/ra_hir_ty/src/db.rs | |||
@@ -76,6 +76,8 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> { | |||
76 | #[salsa::interned] | 76 | #[salsa::interned] |
77 | fn intern_type_ctor(&self, type_ctor: TypeCtor) -> crate::TypeCtorId; | 77 | fn intern_type_ctor(&self, type_ctor: TypeCtor) -> crate::TypeCtorId; |
78 | #[salsa::interned] | 78 | #[salsa::interned] |
79 | fn intern_callable_def(&self, callable_def: CallableDef) -> crate::CallableDefId; | ||
80 | #[salsa::interned] | ||
79 | fn intern_type_param_id(&self, param_id: TypeParamId) -> GlobalTypeParamId; | 81 | fn intern_type_param_id(&self, param_id: TypeParamId) -> GlobalTypeParamId; |
80 | #[salsa::interned] | 82 | #[salsa::interned] |
81 | fn intern_chalk_impl(&self, impl_: Impl) -> crate::traits::GlobalImplId; | 83 | fn intern_chalk_impl(&self, impl_: Impl) -> crate::traits::GlobalImplId; |
@@ -89,11 +91,14 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> { | |||
89 | fn trait_datum(&self, krate: CrateId, trait_id: chalk::TraitId) -> Arc<chalk::TraitDatum>; | 91 | fn trait_datum(&self, krate: CrateId, trait_id: chalk::TraitId) -> Arc<chalk::TraitDatum>; |
90 | 92 | ||
91 | #[salsa::invoke(chalk::struct_datum_query)] | 93 | #[salsa::invoke(chalk::struct_datum_query)] |
92 | fn struct_datum(&self, krate: CrateId, struct_id: chalk::StructId) -> Arc<chalk::StructDatum>; | 94 | fn struct_datum(&self, krate: CrateId, struct_id: chalk::AdtId) -> Arc<chalk::StructDatum>; |
93 | 95 | ||
94 | #[salsa::invoke(crate::traits::chalk::impl_datum_query)] | 96 | #[salsa::invoke(crate::traits::chalk::impl_datum_query)] |
95 | fn impl_datum(&self, krate: CrateId, impl_id: chalk::ImplId) -> Arc<chalk::ImplDatum>; | 97 | fn impl_datum(&self, krate: CrateId, impl_id: chalk::ImplId) -> Arc<chalk::ImplDatum>; |
96 | 98 | ||
99 | #[salsa::invoke(crate::traits::chalk::fn_def_datum_query)] | ||
100 | fn fn_def_datum(&self, krate: CrateId, fn_def_id: chalk::FnDefId) -> Arc<chalk::FnDefDatum>; | ||
101 | |||
97 | #[salsa::invoke(crate::traits::chalk::associated_ty_value_query)] | 102 | #[salsa::invoke(crate::traits::chalk::associated_ty_value_query)] |
98 | fn associated_ty_value( | 103 | fn associated_ty_value( |
99 | &self, | 104 | &self, |
diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs index c87ee06ce..93cb45a64 100644 --- a/crates/ra_hir_ty/src/lib.rs +++ b/crates/ra_hir_ty/src/lib.rs | |||
@@ -155,10 +155,16 @@ pub enum TypeCtor { | |||
155 | /// This exists just for Chalk, because Chalk just has a single `StructId` where | 155 | /// This exists just for Chalk, because Chalk just has a single `StructId` where |
156 | /// we have different kinds of ADTs, primitive types and special type | 156 | /// we have different kinds of ADTs, primitive types and special type |
157 | /// constructors like tuples and function pointers. | 157 | /// constructors like tuples and function pointers. |
158 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | 158 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] |
159 | pub struct TypeCtorId(salsa::InternId); | 159 | pub struct TypeCtorId(salsa::InternId); |
160 | impl_intern_key!(TypeCtorId); | 160 | impl_intern_key!(TypeCtorId); |
161 | 161 | ||
162 | /// This exists just for Chalk, because Chalk just has a single `FnDefId` where | ||
163 | /// we have different IDs for struct and enum variant constructors. | ||
164 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] | ||
165 | pub struct CallableDefId(salsa::InternId); | ||
166 | impl_intern_key!(CallableDefId); | ||
167 | |||
162 | impl TypeCtor { | 168 | impl TypeCtor { |
163 | pub fn num_ty_params(self, db: &dyn HirDatabase) -> usize { | 169 | pub fn num_ty_params(self, db: &dyn HirDatabase) -> usize { |
164 | match self { | 170 | match self { |
diff --git a/crates/ra_hir_ty/src/tests/traits.rs b/crates/ra_hir_ty/src/tests/traits.rs index 34f4b9039..0419bc751 100644 --- a/crates/ra_hir_ty/src/tests/traits.rs +++ b/crates/ra_hir_ty/src/tests/traits.rs | |||
@@ -2602,3 +2602,155 @@ fn test(x: &dyn Foo) { | |||
2602 | "### | 2602 | "### |
2603 | ); | 2603 | ); |
2604 | } | 2604 | } |
2605 | |||
2606 | #[test] | ||
2607 | fn builtin_copy() { | ||
2608 | assert_snapshot!( | ||
2609 | infer_with_mismatches(r#" | ||
2610 | #[lang = "copy"] | ||
2611 | trait Copy {} | ||
2612 | |||
2613 | struct IsCopy; | ||
2614 | impl Copy for IsCopy {} | ||
2615 | struct NotCopy; | ||
2616 | |||
2617 | trait Test { fn test(&self) -> bool; } | ||
2618 | impl<T: Copy> Test for T {} | ||
2619 | |||
2620 | fn test() { | ||
2621 | IsCopy.test(); | ||
2622 | NotCopy.test(); | ||
2623 | (IsCopy, IsCopy).test(); | ||
2624 | (IsCopy, NotCopy).test(); | ||
2625 | } | ||
2626 | "#, true), | ||
2627 | @r###" | ||
2628 | 111..115 'self': &Self | ||
2629 | 167..268 '{ ...t(); }': () | ||
2630 | 173..179 'IsCopy': IsCopy | ||
2631 | 173..186 'IsCopy.test()': bool | ||
2632 | 192..199 'NotCopy': NotCopy | ||
2633 | 192..206 'NotCopy.test()': {unknown} | ||
2634 | 212..228 '(IsCop...sCopy)': (IsCopy, IsCopy) | ||
2635 | 212..235 '(IsCop...test()': bool | ||
2636 | 213..219 'IsCopy': IsCopy | ||
2637 | 221..227 'IsCopy': IsCopy | ||
2638 | 241..258 '(IsCop...tCopy)': (IsCopy, NotCopy) | ||
2639 | 241..265 '(IsCop...test()': {unknown} | ||
2640 | 242..248 'IsCopy': IsCopy | ||
2641 | 250..257 'NotCopy': NotCopy | ||
2642 | "### | ||
2643 | ); | ||
2644 | } | ||
2645 | |||
2646 | #[test] | ||
2647 | fn builtin_fn_def_copy() { | ||
2648 | assert_snapshot!( | ||
2649 | infer_with_mismatches(r#" | ||
2650 | #[lang = "copy"] | ||
2651 | trait Copy {} | ||
2652 | |||
2653 | fn foo() {} | ||
2654 | fn bar<T: Copy>(T) -> T {} | ||
2655 | struct Struct(usize); | ||
2656 | enum Enum { Variant(usize) } | ||
2657 | |||
2658 | trait Test { fn test(&self) -> bool; } | ||
2659 | impl<T: Copy> Test for T {} | ||
2660 | |||
2661 | fn test() { | ||
2662 | foo.test(); | ||
2663 | bar.test(); | ||
2664 | Struct.test(); | ||
2665 | Enum::Variant.test(); | ||
2666 | } | ||
2667 | "#, true), | ||
2668 | // wrong result, because the built-in Copy impl for fn defs doesn't exist in Chalk yet | ||
2669 | @r###" | ||
2670 | 42..44 '{}': () | ||
2671 | 61..62 'T': {unknown} | ||
2672 | 69..71 '{}': () | ||
2673 | 69..71: expected T, got () | ||
2674 | 146..150 'self': &Self | ||
2675 | 202..282 '{ ...t(); }': () | ||
2676 | 208..211 'foo': fn foo() | ||
2677 | 208..218 'foo.test()': {unknown} | ||
2678 | 224..227 'bar': fn bar<{unknown}>({unknown}) -> {unknown} | ||
2679 | 224..234 'bar.test()': {unknown} | ||
2680 | 240..246 'Struct': Struct(usize) -> Struct | ||
2681 | 240..253 'Struct.test()': {unknown} | ||
2682 | 259..272 'Enum::Variant': Variant(usize) -> Enum | ||
2683 | 259..279 'Enum::...test()': {unknown} | ||
2684 | "### | ||
2685 | ); | ||
2686 | } | ||
2687 | |||
2688 | #[test] | ||
2689 | fn builtin_fn_ptr_copy() { | ||
2690 | assert_snapshot!( | ||
2691 | infer_with_mismatches(r#" | ||
2692 | #[lang = "copy"] | ||
2693 | trait Copy {} | ||
2694 | |||
2695 | trait Test { fn test(&self) -> bool; } | ||
2696 | impl<T: Copy> Test for T {} | ||
2697 | |||
2698 | fn test(f1: fn(), f2: fn(usize) -> u8, f3: fn(u8, u8) -> &u8) { | ||
2699 | f1.test(); | ||
2700 | f2.test(); | ||
2701 | f3.test(); | ||
2702 | } | ||
2703 | "#, true), | ||
2704 | @r###" | ||
2705 | 55..59 'self': &Self | ||
2706 | 109..111 'f1': fn() | ||
2707 | 119..121 'f2': fn(usize) -> u8 | ||
2708 | 140..142 'f3': fn(u8, u8) -> &u8 | ||
2709 | 163..211 '{ ...t(); }': () | ||
2710 | 169..171 'f1': fn() | ||
2711 | 169..178 'f1.test()': bool | ||
2712 | 184..186 'f2': fn(usize) -> u8 | ||
2713 | 184..193 'f2.test()': bool | ||
2714 | 199..201 'f3': fn(u8, u8) -> &u8 | ||
2715 | 199..208 'f3.test()': bool | ||
2716 | "### | ||
2717 | ); | ||
2718 | } | ||
2719 | |||
2720 | #[test] | ||
2721 | fn builtin_sized() { | ||
2722 | assert_snapshot!( | ||
2723 | infer_with_mismatches(r#" | ||
2724 | #[lang = "sized"] | ||
2725 | trait Sized {} | ||
2726 | |||
2727 | trait Test { fn test(&self) -> bool; } | ||
2728 | impl<T: Sized> Test for T {} | ||
2729 | |||
2730 | fn test() { | ||
2731 | 1u8.test(); | ||
2732 | (*"foo").test(); // not Sized | ||
2733 | (1u8, 1u8).test(); | ||
2734 | (1u8, *"foo").test(); // not Sized | ||
2735 | } | ||
2736 | "#, true), | ||
2737 | @r###" | ||
2738 | 57..61 'self': &Self | ||
2739 | 114..229 '{ ...ized }': () | ||
2740 | 120..123 '1u8': u8 | ||
2741 | 120..130 '1u8.test()': bool | ||
2742 | 136..151 '(*"foo").test()': {unknown} | ||
2743 | 137..143 '*"foo"': str | ||
2744 | 138..143 '"foo"': &str | ||
2745 | 170..180 '(1u8, 1u8)': (u8, u8) | ||
2746 | 170..187 '(1u8, ...test()': bool | ||
2747 | 171..174 '1u8': u8 | ||
2748 | 176..179 '1u8': u8 | ||
2749 | 193..206 '(1u8, *"foo")': (u8, str) | ||
2750 | 193..213 '(1u8, ...test()': {unknown} | ||
2751 | 194..197 '1u8': u8 | ||
2752 | 199..205 '*"foo"': str | ||
2753 | 200..205 '"foo"': &str | ||
2754 | "### | ||
2755 | ); | ||
2756 | } | ||
diff --git a/crates/ra_hir_ty/src/traits/chalk.rs b/crates/ra_hir_ty/src/traits/chalk.rs index 5870618a0..5b0f12a3c 100644 --- a/crates/ra_hir_ty/src/traits/chalk.rs +++ b/crates/ra_hir_ty/src/traits/chalk.rs | |||
@@ -1,301 +1,29 @@ | |||
1 | //! Conversion code from/to Chalk. | 1 | //! Conversion code from/to Chalk. |
2 | use std::{fmt, sync::Arc}; | 2 | use std::sync::Arc; |
3 | 3 | ||
4 | use log::debug; | 4 | use log::debug; |
5 | 5 | ||
6 | use chalk_ir::{ | 6 | use chalk_ir::{fold::shift::Shift, GenericArg, TypeName}; |
7 | cast::Cast, fold::shift::Shift, interner::HasInterner, Goal, GoalData, Parameter, | ||
8 | PlaceholderIndex, TypeName, UniverseIndex, | ||
9 | }; | ||
10 | 7 | ||
11 | use hir_def::{AssocContainerId, AssocItemId, GenericDefId, HasModule, Lookup, TypeAliasId}; | 8 | use hir_def::{ |
12 | use ra_db::{ | 9 | lang_item::{lang_attr, LangItemTarget}, |
13 | salsa::{InternId, InternKey}, | 10 | AssocContainerId, AssocItemId, HasModule, Lookup, TypeAliasId, |
14 | CrateId, | ||
15 | }; | 11 | }; |
12 | use ra_db::{salsa::InternKey, CrateId}; | ||
16 | 13 | ||
17 | use super::{builtin, AssocTyValue, Canonical, ChalkContext, Impl, Obligation}; | 14 | use super::{builtin, AssocTyValue, ChalkContext, Impl}; |
18 | use crate::{ | 15 | use crate::{ |
19 | db::HirDatabase, display::HirDisplay, method_resolution::TyFingerprint, utils::generics, | 16 | db::HirDatabase, display::HirDisplay, method_resolution::TyFingerprint, utils::generics, |
20 | ApplicationTy, DebruijnIndex, GenericPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, | 17 | CallableDef, DebruijnIndex, GenericPredicate, Substs, Ty, TypeCtor, |
21 | }; | 18 | }; |
19 | use chalk_rust_ir::WellKnownTrait; | ||
20 | use mapping::{convert_where_clauses, generic_predicate_to_inline_bound, make_binders}; | ||
22 | 21 | ||
23 | pub(super) mod tls; | 22 | pub use self::interner::*; |
24 | |||
25 | #[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] | ||
26 | pub struct Interner; | ||
27 | |||
28 | impl chalk_ir::interner::Interner for Interner { | ||
29 | type InternedType = Box<chalk_ir::TyData<Self>>; | ||
30 | type InternedLifetime = chalk_ir::LifetimeData<Self>; | ||
31 | type InternedParameter = chalk_ir::ParameterData<Self>; | ||
32 | type InternedGoal = Arc<GoalData<Self>>; | ||
33 | type InternedGoals = Vec<Goal<Self>>; | ||
34 | type InternedSubstitution = Vec<Parameter<Self>>; | ||
35 | type InternedProgramClause = chalk_ir::ProgramClauseData<Self>; | ||
36 | type InternedProgramClauses = Arc<[chalk_ir::ProgramClause<Self>]>; | ||
37 | type InternedQuantifiedWhereClauses = Vec<chalk_ir::QuantifiedWhereClause<Self>>; | ||
38 | type InternedParameterKinds = Vec<chalk_ir::ParameterKind<()>>; | ||
39 | type InternedCanonicalVarKinds = Vec<chalk_ir::ParameterKind<UniverseIndex>>; | ||
40 | type Identifier = TypeAliasId; | ||
41 | type DefId = InternId; | ||
42 | |||
43 | fn debug_struct_id( | ||
44 | type_kind_id: StructId, | ||
45 | fmt: &mut fmt::Formatter<'_>, | ||
46 | ) -> Option<fmt::Result> { | ||
47 | tls::with_current_program(|prog| Some(prog?.debug_struct_id(type_kind_id, fmt))) | ||
48 | } | ||
49 | |||
50 | fn debug_trait_id(type_kind_id: TraitId, fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> { | ||
51 | tls::with_current_program(|prog| Some(prog?.debug_trait_id(type_kind_id, fmt))) | ||
52 | } | ||
53 | |||
54 | fn debug_assoc_type_id(id: AssocTypeId, fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> { | ||
55 | tls::with_current_program(|prog| Some(prog?.debug_assoc_type_id(id, fmt))) | ||
56 | } | ||
57 | |||
58 | fn debug_alias( | ||
59 | alias: &chalk_ir::AliasTy<Interner>, | ||
60 | fmt: &mut fmt::Formatter<'_>, | ||
61 | ) -> Option<fmt::Result> { | ||
62 | tls::with_current_program(|prog| Some(prog?.debug_alias(alias, fmt))) | ||
63 | } | ||
64 | |||
65 | fn debug_projection_ty( | ||
66 | proj: &chalk_ir::ProjectionTy<Interner>, | ||
67 | fmt: &mut fmt::Formatter<'_>, | ||
68 | ) -> Option<fmt::Result> { | ||
69 | tls::with_current_program(|prog| Some(prog?.debug_projection_ty(proj, fmt))) | ||
70 | } | ||
71 | |||
72 | fn debug_opaque_ty( | ||
73 | opaque_ty: &chalk_ir::OpaqueTy<Interner>, | ||
74 | fmt: &mut fmt::Formatter<'_>, | ||
75 | ) -> Option<fmt::Result> { | ||
76 | tls::with_current_program(|prog| Some(prog?.debug_opaque_ty(opaque_ty, fmt))) | ||
77 | } | ||
78 | |||
79 | fn debug_opaque_ty_id( | ||
80 | opaque_ty_id: chalk_ir::OpaqueTyId<Self>, | ||
81 | fmt: &mut fmt::Formatter<'_>, | ||
82 | ) -> Option<fmt::Result> { | ||
83 | tls::with_current_program(|prog| Some(prog?.debug_opaque_ty_id(opaque_ty_id, fmt))) | ||
84 | } | ||
85 | |||
86 | fn debug_ty(ty: &chalk_ir::Ty<Interner>, fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> { | ||
87 | tls::with_current_program(|prog| Some(prog?.debug_ty(ty, fmt))) | ||
88 | } | ||
89 | |||
90 | fn debug_lifetime( | ||
91 | lifetime: &chalk_ir::Lifetime<Interner>, | ||
92 | fmt: &mut fmt::Formatter<'_>, | ||
93 | ) -> Option<fmt::Result> { | ||
94 | tls::with_current_program(|prog| Some(prog?.debug_lifetime(lifetime, fmt))) | ||
95 | } | ||
96 | |||
97 | fn debug_parameter( | ||
98 | parameter: &Parameter<Interner>, | ||
99 | fmt: &mut fmt::Formatter<'_>, | ||
100 | ) -> Option<fmt::Result> { | ||
101 | tls::with_current_program(|prog| Some(prog?.debug_parameter(parameter, fmt))) | ||
102 | } | ||
103 | |||
104 | fn debug_goal(goal: &Goal<Interner>, fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> { | ||
105 | tls::with_current_program(|prog| Some(prog?.debug_goal(goal, fmt))) | ||
106 | } | ||
107 | |||
108 | fn debug_goals( | ||
109 | goals: &chalk_ir::Goals<Interner>, | ||
110 | fmt: &mut fmt::Formatter<'_>, | ||
111 | ) -> Option<fmt::Result> { | ||
112 | tls::with_current_program(|prog| Some(prog?.debug_goals(goals, fmt))) | ||
113 | } | ||
114 | |||
115 | fn debug_program_clause_implication( | ||
116 | pci: &chalk_ir::ProgramClauseImplication<Interner>, | ||
117 | fmt: &mut fmt::Formatter<'_>, | ||
118 | ) -> Option<fmt::Result> { | ||
119 | tls::with_current_program(|prog| Some(prog?.debug_program_clause_implication(pci, fmt))) | ||
120 | } | ||
121 | |||
122 | fn debug_application_ty( | ||
123 | application_ty: &chalk_ir::ApplicationTy<Interner>, | ||
124 | fmt: &mut fmt::Formatter<'_>, | ||
125 | ) -> Option<fmt::Result> { | ||
126 | tls::with_current_program(|prog| Some(prog?.debug_application_ty(application_ty, fmt))) | ||
127 | } | ||
128 | |||
129 | fn debug_substitution( | ||
130 | substitution: &chalk_ir::Substitution<Interner>, | ||
131 | fmt: &mut fmt::Formatter<'_>, | ||
132 | ) -> Option<fmt::Result> { | ||
133 | tls::with_current_program(|prog| Some(prog?.debug_substitution(substitution, fmt))) | ||
134 | } | ||
135 | |||
136 | fn debug_separator_trait_ref( | ||
137 | separator_trait_ref: &chalk_ir::SeparatorTraitRef<Interner>, | ||
138 | fmt: &mut fmt::Formatter<'_>, | ||
139 | ) -> Option<fmt::Result> { | ||
140 | tls::with_current_program(|prog| { | ||
141 | Some(prog?.debug_separator_trait_ref(separator_trait_ref, fmt)) | ||
142 | }) | ||
143 | } | ||
144 | |||
145 | fn intern_ty(&self, ty: chalk_ir::TyData<Self>) -> Box<chalk_ir::TyData<Self>> { | ||
146 | Box::new(ty) | ||
147 | } | ||
148 | |||
149 | fn ty_data<'a>(&self, ty: &'a Box<chalk_ir::TyData<Self>>) -> &'a chalk_ir::TyData<Self> { | ||
150 | ty | ||
151 | } | ||
152 | |||
153 | fn intern_lifetime( | ||
154 | &self, | ||
155 | lifetime: chalk_ir::LifetimeData<Self>, | ||
156 | ) -> chalk_ir::LifetimeData<Self> { | ||
157 | lifetime | ||
158 | } | ||
159 | |||
160 | fn lifetime_data<'a>( | ||
161 | &self, | ||
162 | lifetime: &'a chalk_ir::LifetimeData<Self>, | ||
163 | ) -> &'a chalk_ir::LifetimeData<Self> { | ||
164 | lifetime | ||
165 | } | ||
166 | |||
167 | fn intern_parameter( | ||
168 | &self, | ||
169 | parameter: chalk_ir::ParameterData<Self>, | ||
170 | ) -> chalk_ir::ParameterData<Self> { | ||
171 | parameter | ||
172 | } | ||
173 | |||
174 | fn parameter_data<'a>( | ||
175 | &self, | ||
176 | parameter: &'a chalk_ir::ParameterData<Self>, | ||
177 | ) -> &'a chalk_ir::ParameterData<Self> { | ||
178 | parameter | ||
179 | } | ||
180 | |||
181 | fn intern_goal(&self, goal: GoalData<Self>) -> Arc<GoalData<Self>> { | ||
182 | Arc::new(goal) | ||
183 | } | ||
184 | |||
185 | fn intern_goals<E>( | ||
186 | &self, | ||
187 | data: impl IntoIterator<Item = Result<Goal<Self>, E>>, | ||
188 | ) -> Result<Self::InternedGoals, E> { | ||
189 | data.into_iter().collect() | ||
190 | } | ||
191 | |||
192 | fn goal_data<'a>(&self, goal: &'a Arc<GoalData<Self>>) -> &'a GoalData<Self> { | ||
193 | goal | ||
194 | } | ||
195 | |||
196 | fn goals_data<'a>(&self, goals: &'a Vec<Goal<Interner>>) -> &'a [Goal<Interner>] { | ||
197 | goals | ||
198 | } | ||
199 | |||
200 | fn intern_substitution<E>( | ||
201 | &self, | ||
202 | data: impl IntoIterator<Item = Result<Parameter<Self>, E>>, | ||
203 | ) -> Result<Vec<Parameter<Self>>, E> { | ||
204 | data.into_iter().collect() | ||
205 | } | ||
206 | |||
207 | fn substitution_data<'a>( | ||
208 | &self, | ||
209 | substitution: &'a Vec<Parameter<Self>>, | ||
210 | ) -> &'a [Parameter<Self>] { | ||
211 | substitution | ||
212 | } | ||
213 | |||
214 | fn intern_program_clause( | ||
215 | &self, | ||
216 | data: chalk_ir::ProgramClauseData<Self>, | ||
217 | ) -> chalk_ir::ProgramClauseData<Self> { | ||
218 | data | ||
219 | } | ||
220 | |||
221 | fn program_clause_data<'a>( | ||
222 | &self, | ||
223 | clause: &'a chalk_ir::ProgramClauseData<Self>, | ||
224 | ) -> &'a chalk_ir::ProgramClauseData<Self> { | ||
225 | clause | ||
226 | } | ||
227 | |||
228 | fn intern_program_clauses<E>( | ||
229 | &self, | ||
230 | data: impl IntoIterator<Item = Result<chalk_ir::ProgramClause<Self>, E>>, | ||
231 | ) -> Result<Arc<[chalk_ir::ProgramClause<Self>]>, E> { | ||
232 | data.into_iter().collect() | ||
233 | } | ||
234 | |||
235 | fn program_clauses_data<'a>( | ||
236 | &self, | ||
237 | clauses: &'a Arc<[chalk_ir::ProgramClause<Self>]>, | ||
238 | ) -> &'a [chalk_ir::ProgramClause<Self>] { | ||
239 | &clauses | ||
240 | } | ||
241 | |||
242 | fn intern_quantified_where_clauses<E>( | ||
243 | &self, | ||
244 | data: impl IntoIterator<Item = Result<chalk_ir::QuantifiedWhereClause<Self>, E>>, | ||
245 | ) -> Result<Self::InternedQuantifiedWhereClauses, E> { | ||
246 | data.into_iter().collect() | ||
247 | } | ||
248 | |||
249 | fn quantified_where_clauses_data<'a>( | ||
250 | &self, | ||
251 | clauses: &'a Self::InternedQuantifiedWhereClauses, | ||
252 | ) -> &'a [chalk_ir::QuantifiedWhereClause<Self>] { | ||
253 | clauses | ||
254 | } | ||
255 | |||
256 | fn intern_parameter_kinds<E>( | ||
257 | &self, | ||
258 | data: impl IntoIterator<Item = Result<chalk_ir::ParameterKind<()>, E>>, | ||
259 | ) -> Result<Self::InternedParameterKinds, E> { | ||
260 | data.into_iter().collect() | ||
261 | } | ||
262 | 23 | ||
263 | fn parameter_kinds_data<'a>( | 24 | pub(super) mod tls; |
264 | &self, | 25 | mod interner; |
265 | parameter_kinds: &'a Self::InternedParameterKinds, | 26 | mod mapping; |
266 | ) -> &'a [chalk_ir::ParameterKind<()>] { | ||
267 | ¶meter_kinds | ||
268 | } | ||
269 | |||
270 | fn intern_canonical_var_kinds<E>( | ||
271 | &self, | ||
272 | data: impl IntoIterator<Item = Result<chalk_ir::ParameterKind<UniverseIndex>, E>>, | ||
273 | ) -> Result<Self::InternedCanonicalVarKinds, E> { | ||
274 | data.into_iter().collect() | ||
275 | } | ||
276 | |||
277 | fn canonical_var_kinds_data<'a>( | ||
278 | &self, | ||
279 | canonical_var_kinds: &'a Self::InternedCanonicalVarKinds, | ||
280 | ) -> &'a [chalk_ir::ParameterKind<UniverseIndex>] { | ||
281 | &canonical_var_kinds | ||
282 | } | ||
283 | } | ||
284 | |||
285 | impl chalk_ir::interner::HasInterner for Interner { | ||
286 | type Interner = Self; | ||
287 | } | ||
288 | |||
289 | pub type AssocTypeId = chalk_ir::AssocTypeId<Interner>; | ||
290 | pub type AssociatedTyDatum = chalk_rust_ir::AssociatedTyDatum<Interner>; | ||
291 | pub type TraitId = chalk_ir::TraitId<Interner>; | ||
292 | pub type TraitDatum = chalk_rust_ir::TraitDatum<Interner>; | ||
293 | pub type StructId = chalk_ir::StructId<Interner>; | ||
294 | pub type StructDatum = chalk_rust_ir::StructDatum<Interner>; | ||
295 | pub type ImplId = chalk_ir::ImplId<Interner>; | ||
296 | pub type ImplDatum = chalk_rust_ir::ImplDatum<Interner>; | ||
297 | pub type AssociatedTyValueId = chalk_rust_ir::AssociatedTyValueId<Interner>; | ||
298 | pub type AssociatedTyValue = chalk_rust_ir::AssociatedTyValue<Interner>; | ||
299 | 27 | ||
300 | pub(super) trait ToChalk { | 28 | pub(super) trait ToChalk { |
301 | type Chalk; | 29 | type Chalk; |
@@ -310,508 +38,6 @@ where | |||
310 | T::from_chalk(db, chalk) | 38 | T::from_chalk(db, chalk) |
311 | } | 39 | } |
312 | 40 | ||
313 | impl ToChalk for Ty { | ||
314 | type Chalk = chalk_ir::Ty<Interner>; | ||
315 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::Ty<Interner> { | ||
316 | match self { | ||
317 | Ty::Apply(apply_ty) => { | ||
318 | let name = apply_ty.ctor.to_chalk(db); | ||
319 | let substitution = apply_ty.parameters.to_chalk(db); | ||
320 | chalk_ir::ApplicationTy { name, substitution }.cast(&Interner).intern(&Interner) | ||
321 | } | ||
322 | Ty::Projection(proj_ty) => { | ||
323 | let associated_ty_id = proj_ty.associated_ty.to_chalk(db); | ||
324 | let substitution = proj_ty.parameters.to_chalk(db); | ||
325 | chalk_ir::AliasTy::Projection(chalk_ir::ProjectionTy { | ||
326 | associated_ty_id, | ||
327 | substitution, | ||
328 | }) | ||
329 | .cast(&Interner) | ||
330 | .intern(&Interner) | ||
331 | } | ||
332 | Ty::Placeholder(id) => { | ||
333 | let interned_id = db.intern_type_param_id(id); | ||
334 | PlaceholderIndex { | ||
335 | ui: UniverseIndex::ROOT, | ||
336 | idx: interned_id.as_intern_id().as_usize(), | ||
337 | } | ||
338 | .to_ty::<Interner>(&Interner) | ||
339 | } | ||
340 | Ty::Bound(idx) => chalk_ir::TyData::BoundVar(idx).intern(&Interner), | ||
341 | Ty::Infer(_infer_ty) => panic!("uncanonicalized infer ty"), | ||
342 | Ty::Dyn(predicates) => { | ||
343 | let where_clauses = chalk_ir::QuantifiedWhereClauses::from( | ||
344 | &Interner, | ||
345 | predicates.iter().filter(|p| !p.is_error()).cloned().map(|p| p.to_chalk(db)), | ||
346 | ); | ||
347 | let bounded_ty = chalk_ir::DynTy { bounds: make_binders(where_clauses, 1) }; | ||
348 | chalk_ir::TyData::Dyn(bounded_ty).intern(&Interner) | ||
349 | } | ||
350 | Ty::Opaque(_) | Ty::Unknown => { | ||
351 | let substitution = chalk_ir::Substitution::empty(&Interner); | ||
352 | let name = TypeName::Error; | ||
353 | chalk_ir::ApplicationTy { name, substitution }.cast(&Interner).intern(&Interner) | ||
354 | } | ||
355 | } | ||
356 | } | ||
357 | fn from_chalk(db: &dyn HirDatabase, chalk: chalk_ir::Ty<Interner>) -> Self { | ||
358 | match chalk.data(&Interner).clone() { | ||
359 | chalk_ir::TyData::Apply(apply_ty) => match apply_ty.name { | ||
360 | TypeName::Error => Ty::Unknown, | ||
361 | _ => { | ||
362 | let ctor = from_chalk(db, apply_ty.name); | ||
363 | let parameters = from_chalk(db, apply_ty.substitution); | ||
364 | Ty::Apply(ApplicationTy { ctor, parameters }) | ||
365 | } | ||
366 | }, | ||
367 | chalk_ir::TyData::Placeholder(idx) => { | ||
368 | assert_eq!(idx.ui, UniverseIndex::ROOT); | ||
369 | let interned_id = crate::db::GlobalTypeParamId::from_intern_id( | ||
370 | crate::salsa::InternId::from(idx.idx), | ||
371 | ); | ||
372 | Ty::Placeholder(db.lookup_intern_type_param_id(interned_id)) | ||
373 | } | ||
374 | chalk_ir::TyData::Alias(chalk_ir::AliasTy::Projection(proj)) => { | ||
375 | let associated_ty = from_chalk(db, proj.associated_ty_id); | ||
376 | let parameters = from_chalk(db, proj.substitution); | ||
377 | Ty::Projection(ProjectionTy { associated_ty, parameters }) | ||
378 | } | ||
379 | chalk_ir::TyData::Alias(chalk_ir::AliasTy::Opaque(_)) => unimplemented!(), | ||
380 | chalk_ir::TyData::Function(_) => unimplemented!(), | ||
381 | chalk_ir::TyData::BoundVar(idx) => Ty::Bound(idx), | ||
382 | chalk_ir::TyData::InferenceVar(_iv) => Ty::Unknown, | ||
383 | chalk_ir::TyData::Dyn(where_clauses) => { | ||
384 | assert_eq!(where_clauses.bounds.binders.len(&Interner), 1); | ||
385 | let predicates = where_clauses | ||
386 | .bounds | ||
387 | .skip_binders() | ||
388 | .iter(&Interner) | ||
389 | .map(|c| from_chalk(db, c.clone())) | ||
390 | .collect(); | ||
391 | Ty::Dyn(predicates) | ||
392 | } | ||
393 | } | ||
394 | } | ||
395 | } | ||
396 | |||
397 | impl ToChalk for Substs { | ||
398 | type Chalk = chalk_ir::Substitution<Interner>; | ||
399 | |||
400 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::Substitution<Interner> { | ||
401 | chalk_ir::Substitution::from(&Interner, self.iter().map(|ty| ty.clone().to_chalk(db))) | ||
402 | } | ||
403 | |||
404 | fn from_chalk(db: &dyn HirDatabase, parameters: chalk_ir::Substitution<Interner>) -> Substs { | ||
405 | let tys = parameters | ||
406 | .iter(&Interner) | ||
407 | .map(|p| match p.ty(&Interner) { | ||
408 | Some(ty) => from_chalk(db, ty.clone()), | ||
409 | None => unimplemented!(), | ||
410 | }) | ||
411 | .collect(); | ||
412 | Substs(tys) | ||
413 | } | ||
414 | } | ||
415 | |||
416 | impl ToChalk for TraitRef { | ||
417 | type Chalk = chalk_ir::TraitRef<Interner>; | ||
418 | |||
419 | fn to_chalk(self: TraitRef, db: &dyn HirDatabase) -> chalk_ir::TraitRef<Interner> { | ||
420 | let trait_id = self.trait_.to_chalk(db); | ||
421 | let substitution = self.substs.to_chalk(db); | ||
422 | chalk_ir::TraitRef { trait_id, substitution } | ||
423 | } | ||
424 | |||
425 | fn from_chalk(db: &dyn HirDatabase, trait_ref: chalk_ir::TraitRef<Interner>) -> Self { | ||
426 | let trait_ = from_chalk(db, trait_ref.trait_id); | ||
427 | let substs = from_chalk(db, trait_ref.substitution); | ||
428 | TraitRef { trait_, substs } | ||
429 | } | ||
430 | } | ||
431 | |||
432 | impl ToChalk for hir_def::TraitId { | ||
433 | type Chalk = TraitId; | ||
434 | |||
435 | fn to_chalk(self, _db: &dyn HirDatabase) -> TraitId { | ||
436 | chalk_ir::TraitId(self.as_intern_id()) | ||
437 | } | ||
438 | |||
439 | fn from_chalk(_db: &dyn HirDatabase, trait_id: TraitId) -> hir_def::TraitId { | ||
440 | InternKey::from_intern_id(trait_id.0) | ||
441 | } | ||
442 | } | ||
443 | |||
444 | impl ToChalk for TypeCtor { | ||
445 | type Chalk = TypeName<Interner>; | ||
446 | |||
447 | fn to_chalk(self, db: &dyn HirDatabase) -> TypeName<Interner> { | ||
448 | match self { | ||
449 | TypeCtor::AssociatedType(type_alias) => { | ||
450 | let type_id = type_alias.to_chalk(db); | ||
451 | TypeName::AssociatedType(type_id) | ||
452 | } | ||
453 | _ => { | ||
454 | // other TypeCtors get interned and turned into a chalk StructId | ||
455 | let struct_id = db.intern_type_ctor(self).into(); | ||
456 | TypeName::Struct(struct_id) | ||
457 | } | ||
458 | } | ||
459 | } | ||
460 | |||
461 | fn from_chalk(db: &dyn HirDatabase, type_name: TypeName<Interner>) -> TypeCtor { | ||
462 | match type_name { | ||
463 | TypeName::Struct(struct_id) => db.lookup_intern_type_ctor(struct_id.into()), | ||
464 | TypeName::AssociatedType(type_id) => TypeCtor::AssociatedType(from_chalk(db, type_id)), | ||
465 | TypeName::OpaqueType(_) => unreachable!(), | ||
466 | |||
467 | TypeName::Scalar(_) => unreachable!(), | ||
468 | TypeName::Tuple(_) => unreachable!(), | ||
469 | TypeName::Raw(_) => unreachable!(), | ||
470 | TypeName::Slice => unreachable!(), | ||
471 | TypeName::Ref(_) => unreachable!(), | ||
472 | TypeName::Str => unreachable!(), | ||
473 | |||
474 | TypeName::Error => { | ||
475 | // this should not be reached, since we don't represent TypeName::Error with TypeCtor | ||
476 | unreachable!() | ||
477 | } | ||
478 | } | ||
479 | } | ||
480 | } | ||
481 | |||
482 | impl ToChalk for Impl { | ||
483 | type Chalk = ImplId; | ||
484 | |||
485 | fn to_chalk(self, db: &dyn HirDatabase) -> ImplId { | ||
486 | db.intern_chalk_impl(self).into() | ||
487 | } | ||
488 | |||
489 | fn from_chalk(db: &dyn HirDatabase, impl_id: ImplId) -> Impl { | ||
490 | db.lookup_intern_chalk_impl(impl_id.into()) | ||
491 | } | ||
492 | } | ||
493 | |||
494 | impl ToChalk for TypeAliasId { | ||
495 | type Chalk = AssocTypeId; | ||
496 | |||
497 | fn to_chalk(self, _db: &dyn HirDatabase) -> AssocTypeId { | ||
498 | chalk_ir::AssocTypeId(self.as_intern_id()) | ||
499 | } | ||
500 | |||
501 | fn from_chalk(_db: &dyn HirDatabase, type_alias_id: AssocTypeId) -> TypeAliasId { | ||
502 | InternKey::from_intern_id(type_alias_id.0) | ||
503 | } | ||
504 | } | ||
505 | |||
506 | impl ToChalk for AssocTyValue { | ||
507 | type Chalk = AssociatedTyValueId; | ||
508 | |||
509 | fn to_chalk(self, db: &dyn HirDatabase) -> AssociatedTyValueId { | ||
510 | db.intern_assoc_ty_value(self).into() | ||
511 | } | ||
512 | |||
513 | fn from_chalk(db: &dyn HirDatabase, assoc_ty_value_id: AssociatedTyValueId) -> AssocTyValue { | ||
514 | db.lookup_intern_assoc_ty_value(assoc_ty_value_id.into()) | ||
515 | } | ||
516 | } | ||
517 | |||
518 | impl ToChalk for GenericPredicate { | ||
519 | type Chalk = chalk_ir::QuantifiedWhereClause<Interner>; | ||
520 | |||
521 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::QuantifiedWhereClause<Interner> { | ||
522 | match self { | ||
523 | GenericPredicate::Implemented(trait_ref) => { | ||
524 | let chalk_trait_ref = trait_ref.to_chalk(db); | ||
525 | let chalk_trait_ref = chalk_trait_ref.shifted_in(&Interner); | ||
526 | make_binders(chalk_ir::WhereClause::Implemented(chalk_trait_ref), 0) | ||
527 | } | ||
528 | GenericPredicate::Projection(projection_pred) => { | ||
529 | let ty = projection_pred.ty.to_chalk(db).shifted_in(&Interner); | ||
530 | let projection = projection_pred.projection_ty.to_chalk(db).shifted_in(&Interner); | ||
531 | let alias = chalk_ir::AliasTy::Projection(projection); | ||
532 | make_binders(chalk_ir::WhereClause::AliasEq(chalk_ir::AliasEq { alias, ty }), 0) | ||
533 | } | ||
534 | GenericPredicate::Error => panic!("tried passing GenericPredicate::Error to Chalk"), | ||
535 | } | ||
536 | } | ||
537 | |||
538 | fn from_chalk( | ||
539 | db: &dyn HirDatabase, | ||
540 | where_clause: chalk_ir::QuantifiedWhereClause<Interner>, | ||
541 | ) -> GenericPredicate { | ||
542 | // we don't produce any where clauses with binders and can't currently deal with them | ||
543 | match where_clause | ||
544 | .skip_binders() | ||
545 | .shifted_out(&Interner) | ||
546 | .expect("unexpected bound vars in where clause") | ||
547 | { | ||
548 | chalk_ir::WhereClause::Implemented(tr) => { | ||
549 | GenericPredicate::Implemented(from_chalk(db, tr)) | ||
550 | } | ||
551 | chalk_ir::WhereClause::AliasEq(projection_eq) => { | ||
552 | let projection_ty = from_chalk( | ||
553 | db, | ||
554 | match projection_eq.alias { | ||
555 | chalk_ir::AliasTy::Projection(p) => p, | ||
556 | _ => unimplemented!(), | ||
557 | }, | ||
558 | ); | ||
559 | let ty = from_chalk(db, projection_eq.ty); | ||
560 | GenericPredicate::Projection(super::ProjectionPredicate { projection_ty, ty }) | ||
561 | } | ||
562 | } | ||
563 | } | ||
564 | } | ||
565 | |||
566 | impl ToChalk for ProjectionTy { | ||
567 | type Chalk = chalk_ir::ProjectionTy<Interner>; | ||
568 | |||
569 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::ProjectionTy<Interner> { | ||
570 | chalk_ir::ProjectionTy { | ||
571 | associated_ty_id: self.associated_ty.to_chalk(db), | ||
572 | substitution: self.parameters.to_chalk(db), | ||
573 | } | ||
574 | } | ||
575 | |||
576 | fn from_chalk( | ||
577 | db: &dyn HirDatabase, | ||
578 | projection_ty: chalk_ir::ProjectionTy<Interner>, | ||
579 | ) -> ProjectionTy { | ||
580 | ProjectionTy { | ||
581 | associated_ty: from_chalk(db, projection_ty.associated_ty_id), | ||
582 | parameters: from_chalk(db, projection_ty.substitution), | ||
583 | } | ||
584 | } | ||
585 | } | ||
586 | |||
587 | impl ToChalk for super::ProjectionPredicate { | ||
588 | type Chalk = chalk_ir::AliasEq<Interner>; | ||
589 | |||
590 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::AliasEq<Interner> { | ||
591 | chalk_ir::AliasEq { | ||
592 | alias: chalk_ir::AliasTy::Projection(self.projection_ty.to_chalk(db)), | ||
593 | ty: self.ty.to_chalk(db), | ||
594 | } | ||
595 | } | ||
596 | |||
597 | fn from_chalk(_db: &dyn HirDatabase, _normalize: chalk_ir::AliasEq<Interner>) -> Self { | ||
598 | unimplemented!() | ||
599 | } | ||
600 | } | ||
601 | |||
602 | impl ToChalk for Obligation { | ||
603 | type Chalk = chalk_ir::DomainGoal<Interner>; | ||
604 | |||
605 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::DomainGoal<Interner> { | ||
606 | match self { | ||
607 | Obligation::Trait(tr) => tr.to_chalk(db).cast(&Interner), | ||
608 | Obligation::Projection(pr) => pr.to_chalk(db).cast(&Interner), | ||
609 | } | ||
610 | } | ||
611 | |||
612 | fn from_chalk(_db: &dyn HirDatabase, _goal: chalk_ir::DomainGoal<Interner>) -> Self { | ||
613 | unimplemented!() | ||
614 | } | ||
615 | } | ||
616 | |||
617 | impl<T> ToChalk for Canonical<T> | ||
618 | where | ||
619 | T: ToChalk, | ||
620 | T::Chalk: HasInterner<Interner = Interner>, | ||
621 | { | ||
622 | type Chalk = chalk_ir::Canonical<T::Chalk>; | ||
623 | |||
624 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::Canonical<T::Chalk> { | ||
625 | let parameter = chalk_ir::ParameterKind::Ty(chalk_ir::UniverseIndex::ROOT); | ||
626 | let value = self.value.to_chalk(db); | ||
627 | chalk_ir::Canonical { | ||
628 | value, | ||
629 | binders: chalk_ir::CanonicalVarKinds::from(&Interner, vec![parameter; self.num_vars]), | ||
630 | } | ||
631 | } | ||
632 | |||
633 | fn from_chalk(db: &dyn HirDatabase, canonical: chalk_ir::Canonical<T::Chalk>) -> Canonical<T> { | ||
634 | Canonical { | ||
635 | num_vars: canonical.binders.len(&Interner), | ||
636 | value: from_chalk(db, canonical.value), | ||
637 | } | ||
638 | } | ||
639 | } | ||
640 | |||
641 | impl ToChalk for Arc<super::TraitEnvironment> { | ||
642 | type Chalk = chalk_ir::Environment<Interner>; | ||
643 | |||
644 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::Environment<Interner> { | ||
645 | let mut clauses = Vec::new(); | ||
646 | for pred in &self.predicates { | ||
647 | if pred.is_error() { | ||
648 | // for env, we just ignore errors | ||
649 | continue; | ||
650 | } | ||
651 | let program_clause: chalk_ir::ProgramClause<Interner> = | ||
652 | pred.clone().to_chalk(db).cast(&Interner); | ||
653 | clauses.push(program_clause.into_from_env_clause(&Interner)); | ||
654 | } | ||
655 | chalk_ir::Environment::new(&Interner).add_clauses(&Interner, clauses) | ||
656 | } | ||
657 | |||
658 | fn from_chalk( | ||
659 | _db: &dyn HirDatabase, | ||
660 | _env: chalk_ir::Environment<Interner>, | ||
661 | ) -> Arc<super::TraitEnvironment> { | ||
662 | unimplemented!() | ||
663 | } | ||
664 | } | ||
665 | |||
666 | impl<T: ToChalk> ToChalk for super::InEnvironment<T> | ||
667 | where | ||
668 | T::Chalk: chalk_ir::interner::HasInterner<Interner = Interner>, | ||
669 | { | ||
670 | type Chalk = chalk_ir::InEnvironment<T::Chalk>; | ||
671 | |||
672 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::InEnvironment<T::Chalk> { | ||
673 | chalk_ir::InEnvironment { | ||
674 | environment: self.environment.to_chalk(db), | ||
675 | goal: self.value.to_chalk(db), | ||
676 | } | ||
677 | } | ||
678 | |||
679 | fn from_chalk( | ||
680 | db: &dyn HirDatabase, | ||
681 | in_env: chalk_ir::InEnvironment<T::Chalk>, | ||
682 | ) -> super::InEnvironment<T> { | ||
683 | super::InEnvironment { | ||
684 | environment: from_chalk(db, in_env.environment), | ||
685 | value: from_chalk(db, in_env.goal), | ||
686 | } | ||
687 | } | ||
688 | } | ||
689 | |||
690 | impl ToChalk for builtin::BuiltinImplData { | ||
691 | type Chalk = ImplDatum; | ||
692 | |||
693 | fn to_chalk(self, db: &dyn HirDatabase) -> ImplDatum { | ||
694 | let impl_type = chalk_rust_ir::ImplType::External; | ||
695 | let where_clauses = self.where_clauses.into_iter().map(|w| w.to_chalk(db)).collect(); | ||
696 | |||
697 | let impl_datum_bound = | ||
698 | chalk_rust_ir::ImplDatumBound { trait_ref: self.trait_ref.to_chalk(db), where_clauses }; | ||
699 | let associated_ty_value_ids = | ||
700 | self.assoc_ty_values.into_iter().map(|v| v.to_chalk(db)).collect(); | ||
701 | chalk_rust_ir::ImplDatum { | ||
702 | binders: make_binders(impl_datum_bound, self.num_vars), | ||
703 | impl_type, | ||
704 | polarity: chalk_rust_ir::Polarity::Positive, | ||
705 | associated_ty_value_ids, | ||
706 | } | ||
707 | } | ||
708 | |||
709 | fn from_chalk(_db: &dyn HirDatabase, _data: ImplDatum) -> Self { | ||
710 | unimplemented!() | ||
711 | } | ||
712 | } | ||
713 | |||
714 | impl ToChalk for builtin::BuiltinImplAssocTyValueData { | ||
715 | type Chalk = AssociatedTyValue; | ||
716 | |||
717 | fn to_chalk(self, db: &dyn HirDatabase) -> AssociatedTyValue { | ||
718 | let ty = self.value.to_chalk(db); | ||
719 | let value_bound = chalk_rust_ir::AssociatedTyValueBound { ty }; | ||
720 | |||
721 | chalk_rust_ir::AssociatedTyValue { | ||
722 | associated_ty_id: self.assoc_ty_id.to_chalk(db), | ||
723 | impl_id: self.impl_.to_chalk(db), | ||
724 | value: make_binders(value_bound, self.num_vars), | ||
725 | } | ||
726 | } | ||
727 | |||
728 | fn from_chalk( | ||
729 | _db: &dyn HirDatabase, | ||
730 | _data: AssociatedTyValue, | ||
731 | ) -> builtin::BuiltinImplAssocTyValueData { | ||
732 | unimplemented!() | ||
733 | } | ||
734 | } | ||
735 | |||
736 | fn make_binders<T>(value: T, num_vars: usize) -> chalk_ir::Binders<T> | ||
737 | where | ||
738 | T: HasInterner<Interner = Interner>, | ||
739 | { | ||
740 | chalk_ir::Binders::new( | ||
741 | chalk_ir::ParameterKinds::from( | ||
742 | &Interner, | ||
743 | std::iter::repeat(chalk_ir::ParameterKind::Ty(())).take(num_vars), | ||
744 | ), | ||
745 | value, | ||
746 | ) | ||
747 | } | ||
748 | |||
749 | fn convert_where_clauses( | ||
750 | db: &dyn HirDatabase, | ||
751 | def: GenericDefId, | ||
752 | substs: &Substs, | ||
753 | ) -> Vec<chalk_ir::QuantifiedWhereClause<Interner>> { | ||
754 | let generic_predicates = db.generic_predicates(def); | ||
755 | let mut result = Vec::with_capacity(generic_predicates.len()); | ||
756 | for pred in generic_predicates.iter() { | ||
757 | if pred.value.is_error() { | ||
758 | // skip errored predicates completely | ||
759 | continue; | ||
760 | } | ||
761 | result.push(pred.clone().subst(substs).to_chalk(db)); | ||
762 | } | ||
763 | result | ||
764 | } | ||
765 | |||
766 | fn generic_predicate_to_inline_bound( | ||
767 | db: &dyn HirDatabase, | ||
768 | pred: &GenericPredicate, | ||
769 | self_ty: &Ty, | ||
770 | ) -> Option<chalk_rust_ir::InlineBound<Interner>> { | ||
771 | // An InlineBound is like a GenericPredicate, except the self type is left out. | ||
772 | // We don't have a special type for this, but Chalk does. | ||
773 | match pred { | ||
774 | GenericPredicate::Implemented(trait_ref) => { | ||
775 | if &trait_ref.substs[0] != self_ty { | ||
776 | // we can only convert predicates back to type bounds if they | ||
777 | // have the expected self type | ||
778 | return None; | ||
779 | } | ||
780 | let args_no_self = trait_ref.substs[1..] | ||
781 | .iter() | ||
782 | .map(|ty| ty.clone().to_chalk(db).cast(&Interner)) | ||
783 | .collect(); | ||
784 | let trait_bound = | ||
785 | chalk_rust_ir::TraitBound { trait_id: trait_ref.trait_.to_chalk(db), args_no_self }; | ||
786 | Some(chalk_rust_ir::InlineBound::TraitBound(trait_bound)) | ||
787 | } | ||
788 | GenericPredicate::Projection(proj) => { | ||
789 | if &proj.projection_ty.parameters[0] != self_ty { | ||
790 | return None; | ||
791 | } | ||
792 | let trait_ = match proj.projection_ty.associated_ty.lookup(db.upcast()).container { | ||
793 | AssocContainerId::TraitId(t) => t, | ||
794 | _ => panic!("associated type not in trait"), | ||
795 | }; | ||
796 | let args_no_self = proj.projection_ty.parameters[1..] | ||
797 | .iter() | ||
798 | .map(|ty| ty.clone().to_chalk(db).cast(&Interner)) | ||
799 | .collect(); | ||
800 | let alias_eq_bound = chalk_rust_ir::AliasEqBound { | ||
801 | value: proj.ty.clone().to_chalk(db), | ||
802 | trait_bound: chalk_rust_ir::TraitBound { | ||
803 | trait_id: trait_.to_chalk(db), | ||
804 | args_no_self, | ||
805 | }, | ||
806 | associated_ty_id: proj.projection_ty.associated_ty.to_chalk(db), | ||
807 | parameters: Vec::new(), // FIXME we don't support generic associated types yet | ||
808 | }; | ||
809 | Some(chalk_rust_ir::InlineBound::AliasEqBound(alias_eq_bound)) | ||
810 | } | ||
811 | GenericPredicate::Error => None, | ||
812 | } | ||
813 | } | ||
814 | |||
815 | impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> { | 41 | impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> { |
816 | fn associated_ty_data(&self, id: AssocTypeId) -> Arc<AssociatedTyDatum> { | 42 | fn associated_ty_data(&self, id: AssocTypeId) -> Arc<AssociatedTyDatum> { |
817 | self.db.associated_ty_data(id) | 43 | self.db.associated_ty_data(id) |
@@ -819,16 +45,24 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> { | |||
819 | fn trait_datum(&self, trait_id: TraitId) -> Arc<TraitDatum> { | 45 | fn trait_datum(&self, trait_id: TraitId) -> Arc<TraitDatum> { |
820 | self.db.trait_datum(self.krate, trait_id) | 46 | self.db.trait_datum(self.krate, trait_id) |
821 | } | 47 | } |
822 | fn struct_datum(&self, struct_id: StructId) -> Arc<StructDatum> { | 48 | fn adt_datum(&self, struct_id: AdtId) -> Arc<StructDatum> { |
823 | self.db.struct_datum(self.krate, struct_id) | 49 | self.db.struct_datum(self.krate, struct_id) |
824 | } | 50 | } |
825 | fn impl_datum(&self, impl_id: ImplId) -> Arc<ImplDatum> { | 51 | fn impl_datum(&self, impl_id: ImplId) -> Arc<ImplDatum> { |
826 | self.db.impl_datum(self.krate, impl_id) | 52 | self.db.impl_datum(self.krate, impl_id) |
827 | } | 53 | } |
54 | |||
55 | fn fn_def_datum( | ||
56 | &self, | ||
57 | fn_def_id: chalk_ir::FnDefId<Interner>, | ||
58 | ) -> Arc<chalk_rust_ir::FnDefDatum<Interner>> { | ||
59 | self.db.fn_def_datum(self.krate, fn_def_id) | ||
60 | } | ||
61 | |||
828 | fn impls_for_trait( | 62 | fn impls_for_trait( |
829 | &self, | 63 | &self, |
830 | trait_id: TraitId, | 64 | trait_id: TraitId, |
831 | parameters: &[Parameter<Interner>], | 65 | parameters: &[GenericArg<Interner>], |
832 | ) -> Vec<ImplId> { | 66 | ) -> Vec<ImplId> { |
833 | debug!("impls_for_trait {:?}", trait_id); | 67 | debug!("impls_for_trait {:?}", trait_id); |
834 | let trait_: hir_def::TraitId = from_chalk(self.db, trait_id); | 68 | let trait_: hir_def::TraitId = from_chalk(self.db, trait_id); |
@@ -859,7 +93,7 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> { | |||
859 | debug!("impls_for_trait returned {} impls", result.len()); | 93 | debug!("impls_for_trait returned {} impls", result.len()); |
860 | result | 94 | result |
861 | } | 95 | } |
862 | fn impl_provided_for(&self, auto_trait_id: TraitId, struct_id: StructId) -> bool { | 96 | fn impl_provided_for(&self, auto_trait_id: TraitId, struct_id: AdtId) -> bool { |
863 | debug!("impl_provided_for {:?}, {:?}", auto_trait_id, struct_id); | 97 | debug!("impl_provided_for {:?}, {:?}", auto_trait_id, struct_id); |
864 | false // FIXME | 98 | false // FIXME |
865 | } | 99 | } |
@@ -878,10 +112,15 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> { | |||
878 | } | 112 | } |
879 | fn well_known_trait_id( | 113 | fn well_known_trait_id( |
880 | &self, | 114 | &self, |
881 | _well_known_trait: chalk_rust_ir::WellKnownTrait, | 115 | well_known_trait: chalk_rust_ir::WellKnownTrait, |
882 | ) -> Option<chalk_ir::TraitId<Interner>> { | 116 | ) -> Option<chalk_ir::TraitId<Interner>> { |
883 | // FIXME tell Chalk about well-known traits (here and in trait_datum) | 117 | let lang_attr = lang_attr_from_well_known_trait(well_known_trait); |
884 | None | 118 | let lang_items = self.db.crate_lang_items(self.krate); |
119 | let trait_ = match lang_items.target(lang_attr) { | ||
120 | Some(LangItemTarget::TraitId(trait_)) => trait_, | ||
121 | _ => return None, | ||
122 | }; | ||
123 | Some(trait_.to_chalk(self.db)) | ||
885 | } | 124 | } |
886 | 125 | ||
887 | fn program_clauses_for_env( | 126 | fn program_clauses_for_env( |
@@ -983,7 +222,8 @@ pub(crate) fn trait_datum_query( | |||
983 | let associated_ty_ids = | 222 | let associated_ty_ids = |
984 | trait_data.associated_types().map(|type_alias| type_alias.to_chalk(db)).collect(); | 223 | trait_data.associated_types().map(|type_alias| type_alias.to_chalk(db)).collect(); |
985 | let trait_datum_bound = chalk_rust_ir::TraitDatumBound { where_clauses }; | 224 | let trait_datum_bound = chalk_rust_ir::TraitDatumBound { where_clauses }; |
986 | let well_known = None; // FIXME set this (depending on lang items) | 225 | let well_known = |
226 | lang_attr(db.upcast(), trait_).and_then(|name| well_known_trait_from_lang_attr(&name)); | ||
987 | let trait_datum = TraitDatum { | 227 | let trait_datum = TraitDatum { |
988 | id: trait_id, | 228 | id: trait_id, |
989 | binders: make_binders(trait_datum_bound, bound_vars.len()), | 229 | binders: make_binders(trait_datum_bound, bound_vars.len()), |
@@ -994,13 +234,32 @@ pub(crate) fn trait_datum_query( | |||
994 | Arc::new(trait_datum) | 234 | Arc::new(trait_datum) |
995 | } | 235 | } |
996 | 236 | ||
237 | fn well_known_trait_from_lang_attr(name: &str) -> Option<WellKnownTrait> { | ||
238 | Some(match name { | ||
239 | "sized" => WellKnownTrait::SizedTrait, | ||
240 | "copy" => WellKnownTrait::CopyTrait, | ||
241 | "clone" => WellKnownTrait::CloneTrait, | ||
242 | "drop" => WellKnownTrait::DropTrait, | ||
243 | _ => return None, | ||
244 | }) | ||
245 | } | ||
246 | |||
247 | fn lang_attr_from_well_known_trait(attr: WellKnownTrait) -> &'static str { | ||
248 | match attr { | ||
249 | WellKnownTrait::SizedTrait => "sized", | ||
250 | WellKnownTrait::CopyTrait => "copy", | ||
251 | WellKnownTrait::CloneTrait => "clone", | ||
252 | WellKnownTrait::DropTrait => "drop", | ||
253 | } | ||
254 | } | ||
255 | |||
997 | pub(crate) fn struct_datum_query( | 256 | pub(crate) fn struct_datum_query( |
998 | db: &dyn HirDatabase, | 257 | db: &dyn HirDatabase, |
999 | krate: CrateId, | 258 | krate: CrateId, |
1000 | struct_id: StructId, | 259 | struct_id: AdtId, |
1001 | ) -> Arc<StructDatum> { | 260 | ) -> Arc<StructDatum> { |
1002 | debug!("struct_datum {:?}", struct_id); | 261 | debug!("struct_datum {:?}", struct_id); |
1003 | let type_ctor: TypeCtor = from_chalk(db, TypeName::Struct(struct_id)); | 262 | let type_ctor: TypeCtor = from_chalk(db, TypeName::Adt(struct_id)); |
1004 | debug!("struct {:?} = {:?}", struct_id, type_ctor); | 263 | debug!("struct {:?} = {:?}", struct_id, type_ctor); |
1005 | let num_params = type_ctor.num_ty_params(db); | 264 | let num_params = type_ctor.num_ty_params(db); |
1006 | let upstream = type_ctor.krate(db) != Some(krate); | 265 | let upstream = type_ctor.krate(db) != Some(krate); |
@@ -1012,12 +271,12 @@ pub(crate) fn struct_datum_query( | |||
1012 | convert_where_clauses(db, generic_def, &bound_vars) | 271 | convert_where_clauses(db, generic_def, &bound_vars) |
1013 | }) | 272 | }) |
1014 | .unwrap_or_else(Vec::new); | 273 | .unwrap_or_else(Vec::new); |
1015 | let flags = chalk_rust_ir::StructFlags { | 274 | let flags = chalk_rust_ir::AdtFlags { |
1016 | upstream, | 275 | upstream, |
1017 | // FIXME set fundamental flag correctly | 276 | // FIXME set fundamental flag correctly |
1018 | fundamental: false, | 277 | fundamental: false, |
1019 | }; | 278 | }; |
1020 | let struct_datum_bound = chalk_rust_ir::StructDatumBound { | 279 | let struct_datum_bound = chalk_rust_ir::AdtDatumBound { |
1021 | fields: Vec::new(), // FIXME add fields (only relevant for auto traits) | 280 | fields: Vec::new(), // FIXME add fields (only relevant for auto traits) |
1022 | where_clauses, | 281 | where_clauses, |
1023 | }; | 282 | }; |
@@ -1145,15 +404,47 @@ fn type_alias_associated_ty_value( | |||
1145 | Arc::new(value) | 404 | Arc::new(value) |
1146 | } | 405 | } |
1147 | 406 | ||
1148 | impl From<StructId> for crate::TypeCtorId { | 407 | pub(crate) fn fn_def_datum_query( |
1149 | fn from(struct_id: StructId) -> Self { | 408 | db: &dyn HirDatabase, |
1150 | InternKey::from_intern_id(struct_id.0) | 409 | _krate: CrateId, |
410 | fn_def_id: FnDefId, | ||
411 | ) -> Arc<FnDefDatum> { | ||
412 | let callable_def: CallableDef = from_chalk(db, fn_def_id); | ||
413 | let generic_params = generics(db.upcast(), callable_def.into()); | ||
414 | let sig = db.callable_item_signature(callable_def); | ||
415 | let bound_vars = Substs::bound_vars(&generic_params, DebruijnIndex::INNERMOST); | ||
416 | let where_clauses = convert_where_clauses(db, callable_def.into(), &bound_vars); | ||
417 | let bound = chalk_rust_ir::FnDefDatumBound { | ||
418 | // Note: Chalk doesn't actually use this information yet as far as I am aware, but we provide it anyway | ||
419 | argument_types: sig.value.params().iter().map(|ty| ty.clone().to_chalk(db)).collect(), | ||
420 | return_type: sig.value.ret().clone().to_chalk(db), | ||
421 | where_clauses, | ||
422 | }; | ||
423 | let datum = FnDefDatum { id: fn_def_id, binders: make_binders(bound, sig.num_binders) }; | ||
424 | Arc::new(datum) | ||
425 | } | ||
426 | |||
427 | impl From<AdtId> for crate::TypeCtorId { | ||
428 | fn from(struct_id: AdtId) -> Self { | ||
429 | struct_id.0 | ||
1151 | } | 430 | } |
1152 | } | 431 | } |
1153 | 432 | ||
1154 | impl From<crate::TypeCtorId> for StructId { | 433 | impl From<crate::TypeCtorId> for AdtId { |
1155 | fn from(type_ctor_id: crate::TypeCtorId) -> Self { | 434 | fn from(type_ctor_id: crate::TypeCtorId) -> Self { |
1156 | chalk_ir::StructId(type_ctor_id.as_intern_id()) | 435 | chalk_ir::AdtId(type_ctor_id) |
436 | } | ||
437 | } | ||
438 | |||
439 | impl From<FnDefId> for crate::CallableDefId { | ||
440 | fn from(fn_def_id: FnDefId) -> Self { | ||
441 | InternKey::from_intern_id(fn_def_id.0) | ||
442 | } | ||
443 | } | ||
444 | |||
445 | impl From<crate::CallableDefId> for FnDefId { | ||
446 | fn from(callable_def_id: crate::CallableDefId) -> Self { | ||
447 | chalk_ir::FnDefId(callable_def_id.as_intern_id()) | ||
1157 | } | 448 | } |
1158 | } | 449 | } |
1159 | 450 | ||
diff --git a/crates/ra_hir_ty/src/traits/chalk/interner.rs b/crates/ra_hir_ty/src/traits/chalk/interner.rs new file mode 100644 index 000000000..2a27f8ed8 --- /dev/null +++ b/crates/ra_hir_ty/src/traits/chalk/interner.rs | |||
@@ -0,0 +1,353 @@ | |||
1 | //! Implementation of the Chalk `Interner` trait, which allows customizing the | ||
2 | //! representation of the various objects Chalk deals with (types, goals etc.). | ||
3 | |||
4 | use super::tls; | ||
5 | use chalk_ir::{GenericArg, Goal, GoalData}; | ||
6 | use hir_def::TypeAliasId; | ||
7 | use ra_db::salsa::InternId; | ||
8 | use std::{fmt, sync::Arc}; | ||
9 | |||
10 | #[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] | ||
11 | pub struct Interner; | ||
12 | |||
13 | pub type AssocTypeId = chalk_ir::AssocTypeId<Interner>; | ||
14 | pub type AssociatedTyDatum = chalk_rust_ir::AssociatedTyDatum<Interner>; | ||
15 | pub type TraitId = chalk_ir::TraitId<Interner>; | ||
16 | pub type TraitDatum = chalk_rust_ir::TraitDatum<Interner>; | ||
17 | pub type AdtId = chalk_ir::AdtId<Interner>; | ||
18 | pub type StructDatum = chalk_rust_ir::AdtDatum<Interner>; | ||
19 | pub type ImplId = chalk_ir::ImplId<Interner>; | ||
20 | pub type ImplDatum = chalk_rust_ir::ImplDatum<Interner>; | ||
21 | pub type AssociatedTyValueId = chalk_rust_ir::AssociatedTyValueId<Interner>; | ||
22 | pub type AssociatedTyValue = chalk_rust_ir::AssociatedTyValue<Interner>; | ||
23 | pub type FnDefId = chalk_ir::FnDefId<Interner>; | ||
24 | pub type FnDefDatum = chalk_rust_ir::FnDefDatum<Interner>; | ||
25 | |||
26 | impl chalk_ir::interner::Interner for Interner { | ||
27 | type InternedType = Box<chalk_ir::TyData<Self>>; // FIXME use Arc? | ||
28 | type InternedLifetime = chalk_ir::LifetimeData<Self>; | ||
29 | type InternedConst = Arc<chalk_ir::ConstData<Self>>; | ||
30 | type InternedConcreteConst = (); | ||
31 | type InternedGenericArg = chalk_ir::GenericArgData<Self>; | ||
32 | type InternedGoal = Arc<GoalData<Self>>; | ||
33 | type InternedGoals = Vec<Goal<Self>>; | ||
34 | type InternedSubstitution = Vec<GenericArg<Self>>; | ||
35 | type InternedProgramClause = chalk_ir::ProgramClauseData<Self>; | ||
36 | type InternedProgramClauses = Arc<[chalk_ir::ProgramClause<Self>]>; | ||
37 | type InternedQuantifiedWhereClauses = Vec<chalk_ir::QuantifiedWhereClause<Self>>; | ||
38 | type InternedVariableKinds = Vec<chalk_ir::VariableKind<Self>>; | ||
39 | type InternedCanonicalVarKinds = Vec<chalk_ir::CanonicalVarKind<Self>>; | ||
40 | type DefId = InternId; | ||
41 | type InternedAdtId = crate::TypeCtorId; | ||
42 | type Identifier = TypeAliasId; | ||
43 | |||
44 | fn debug_adt_id(type_kind_id: AdtId, fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> { | ||
45 | tls::with_current_program(|prog| Some(prog?.debug_struct_id(type_kind_id, fmt))) | ||
46 | } | ||
47 | |||
48 | fn debug_trait_id(type_kind_id: TraitId, fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> { | ||
49 | tls::with_current_program(|prog| Some(prog?.debug_trait_id(type_kind_id, fmt))) | ||
50 | } | ||
51 | |||
52 | fn debug_assoc_type_id(id: AssocTypeId, fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> { | ||
53 | tls::with_current_program(|prog| Some(prog?.debug_assoc_type_id(id, fmt))) | ||
54 | } | ||
55 | |||
56 | fn debug_alias( | ||
57 | alias: &chalk_ir::AliasTy<Interner>, | ||
58 | fmt: &mut fmt::Formatter<'_>, | ||
59 | ) -> Option<fmt::Result> { | ||
60 | tls::with_current_program(|prog| Some(prog?.debug_alias(alias, fmt))) | ||
61 | } | ||
62 | |||
63 | fn debug_projection_ty( | ||
64 | proj: &chalk_ir::ProjectionTy<Interner>, | ||
65 | fmt: &mut fmt::Formatter<'_>, | ||
66 | ) -> Option<fmt::Result> { | ||
67 | tls::with_current_program(|prog| Some(prog?.debug_projection_ty(proj, fmt))) | ||
68 | } | ||
69 | |||
70 | fn debug_opaque_ty( | ||
71 | opaque_ty: &chalk_ir::OpaqueTy<Interner>, | ||
72 | fmt: &mut fmt::Formatter<'_>, | ||
73 | ) -> Option<fmt::Result> { | ||
74 | tls::with_current_program(|prog| Some(prog?.debug_opaque_ty(opaque_ty, fmt))) | ||
75 | } | ||
76 | |||
77 | fn debug_opaque_ty_id( | ||
78 | opaque_ty_id: chalk_ir::OpaqueTyId<Self>, | ||
79 | fmt: &mut fmt::Formatter<'_>, | ||
80 | ) -> Option<fmt::Result> { | ||
81 | tls::with_current_program(|prog| Some(prog?.debug_opaque_ty_id(opaque_ty_id, fmt))) | ||
82 | } | ||
83 | |||
84 | fn debug_ty(ty: &chalk_ir::Ty<Interner>, fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> { | ||
85 | tls::with_current_program(|prog| Some(prog?.debug_ty(ty, fmt))) | ||
86 | } | ||
87 | |||
88 | fn debug_lifetime( | ||
89 | lifetime: &chalk_ir::Lifetime<Interner>, | ||
90 | fmt: &mut fmt::Formatter<'_>, | ||
91 | ) -> Option<fmt::Result> { | ||
92 | tls::with_current_program(|prog| Some(prog?.debug_lifetime(lifetime, fmt))) | ||
93 | } | ||
94 | |||
95 | fn debug_generic_arg( | ||
96 | parameter: &GenericArg<Interner>, | ||
97 | fmt: &mut fmt::Formatter<'_>, | ||
98 | ) -> Option<fmt::Result> { | ||
99 | tls::with_current_program(|prog| Some(prog?.debug_generic_arg(parameter, fmt))) | ||
100 | } | ||
101 | |||
102 | fn debug_goal(goal: &Goal<Interner>, fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> { | ||
103 | tls::with_current_program(|prog| Some(prog?.debug_goal(goal, fmt))) | ||
104 | } | ||
105 | |||
106 | fn debug_goals( | ||
107 | goals: &chalk_ir::Goals<Interner>, | ||
108 | fmt: &mut fmt::Formatter<'_>, | ||
109 | ) -> Option<fmt::Result> { | ||
110 | tls::with_current_program(|prog| Some(prog?.debug_goals(goals, fmt))) | ||
111 | } | ||
112 | |||
113 | fn debug_program_clause_implication( | ||
114 | pci: &chalk_ir::ProgramClauseImplication<Interner>, | ||
115 | fmt: &mut fmt::Formatter<'_>, | ||
116 | ) -> Option<fmt::Result> { | ||
117 | tls::with_current_program(|prog| Some(prog?.debug_program_clause_implication(pci, fmt))) | ||
118 | } | ||
119 | |||
120 | fn debug_application_ty( | ||
121 | application_ty: &chalk_ir::ApplicationTy<Interner>, | ||
122 | fmt: &mut fmt::Formatter<'_>, | ||
123 | ) -> Option<fmt::Result> { | ||
124 | tls::with_current_program(|prog| Some(prog?.debug_application_ty(application_ty, fmt))) | ||
125 | } | ||
126 | |||
127 | fn debug_substitution( | ||
128 | substitution: &chalk_ir::Substitution<Interner>, | ||
129 | fmt: &mut fmt::Formatter<'_>, | ||
130 | ) -> Option<fmt::Result> { | ||
131 | tls::with_current_program(|prog| Some(prog?.debug_substitution(substitution, fmt))) | ||
132 | } | ||
133 | |||
134 | fn debug_separator_trait_ref( | ||
135 | separator_trait_ref: &chalk_ir::SeparatorTraitRef<Interner>, | ||
136 | fmt: &mut fmt::Formatter<'_>, | ||
137 | ) -> Option<fmt::Result> { | ||
138 | tls::with_current_program(|prog| { | ||
139 | Some(prog?.debug_separator_trait_ref(separator_trait_ref, fmt)) | ||
140 | }) | ||
141 | } | ||
142 | |||
143 | fn debug_fn_def_id( | ||
144 | fn_def_id: chalk_ir::FnDefId<Self>, | ||
145 | fmt: &mut fmt::Formatter<'_>, | ||
146 | ) -> Option<fmt::Result> { | ||
147 | tls::with_current_program(|prog| Some(prog?.debug_fn_def_id(fn_def_id, fmt))) | ||
148 | } | ||
149 | fn debug_const( | ||
150 | constant: &chalk_ir::Const<Self>, | ||
151 | fmt: &mut fmt::Formatter<'_>, | ||
152 | ) -> Option<fmt::Result> { | ||
153 | tls::with_current_program(|prog| Some(prog?.debug_const(constant, fmt))) | ||
154 | } | ||
155 | fn debug_variable_kinds( | ||
156 | variable_kinds: &chalk_ir::VariableKinds<Self>, | ||
157 | fmt: &mut fmt::Formatter<'_>, | ||
158 | ) -> Option<fmt::Result> { | ||
159 | tls::with_current_program(|prog| Some(prog?.debug_variable_kinds(variable_kinds, fmt))) | ||
160 | } | ||
161 | fn debug_variable_kinds_with_angles( | ||
162 | variable_kinds: &chalk_ir::VariableKinds<Self>, | ||
163 | fmt: &mut fmt::Formatter<'_>, | ||
164 | ) -> Option<fmt::Result> { | ||
165 | tls::with_current_program(|prog| { | ||
166 | Some(prog?.debug_variable_kinds_with_angles(variable_kinds, fmt)) | ||
167 | }) | ||
168 | } | ||
169 | fn debug_canonical_var_kinds( | ||
170 | canonical_var_kinds: &chalk_ir::CanonicalVarKinds<Self>, | ||
171 | fmt: &mut fmt::Formatter<'_>, | ||
172 | ) -> Option<fmt::Result> { | ||
173 | tls::with_current_program(|prog| { | ||
174 | Some(prog?.debug_canonical_var_kinds(canonical_var_kinds, fmt)) | ||
175 | }) | ||
176 | } | ||
177 | fn debug_program_clause( | ||
178 | clause: &chalk_ir::ProgramClause<Self>, | ||
179 | fmt: &mut fmt::Formatter<'_>, | ||
180 | ) -> Option<fmt::Result> { | ||
181 | tls::with_current_program(|prog| Some(prog?.debug_program_clause(clause, fmt))) | ||
182 | } | ||
183 | fn debug_program_clauses( | ||
184 | clauses: &chalk_ir::ProgramClauses<Self>, | ||
185 | fmt: &mut fmt::Formatter<'_>, | ||
186 | ) -> Option<fmt::Result> { | ||
187 | tls::with_current_program(|prog| Some(prog?.debug_program_clauses(clauses, fmt))) | ||
188 | } | ||
189 | fn debug_quantified_where_clauses( | ||
190 | clauses: &chalk_ir::QuantifiedWhereClauses<Self>, | ||
191 | fmt: &mut fmt::Formatter<'_>, | ||
192 | ) -> Option<fmt::Result> { | ||
193 | tls::with_current_program(|prog| Some(prog?.debug_quantified_where_clauses(clauses, fmt))) | ||
194 | } | ||
195 | |||
196 | fn intern_ty(&self, ty: chalk_ir::TyData<Self>) -> Box<chalk_ir::TyData<Self>> { | ||
197 | Box::new(ty) | ||
198 | } | ||
199 | |||
200 | fn ty_data<'a>(&self, ty: &'a Box<chalk_ir::TyData<Self>>) -> &'a chalk_ir::TyData<Self> { | ||
201 | ty | ||
202 | } | ||
203 | |||
204 | fn intern_lifetime( | ||
205 | &self, | ||
206 | lifetime: chalk_ir::LifetimeData<Self>, | ||
207 | ) -> chalk_ir::LifetimeData<Self> { | ||
208 | lifetime | ||
209 | } | ||
210 | |||
211 | fn lifetime_data<'a>( | ||
212 | &self, | ||
213 | lifetime: &'a chalk_ir::LifetimeData<Self>, | ||
214 | ) -> &'a chalk_ir::LifetimeData<Self> { | ||
215 | lifetime | ||
216 | } | ||
217 | |||
218 | fn intern_const(&self, constant: chalk_ir::ConstData<Self>) -> Arc<chalk_ir::ConstData<Self>> { | ||
219 | Arc::new(constant) | ||
220 | } | ||
221 | |||
222 | fn const_data<'a>( | ||
223 | &self, | ||
224 | constant: &'a Arc<chalk_ir::ConstData<Self>>, | ||
225 | ) -> &'a chalk_ir::ConstData<Self> { | ||
226 | constant | ||
227 | } | ||
228 | |||
229 | fn const_eq(&self, _ty: &Box<chalk_ir::TyData<Self>>, _c1: &(), _c2: &()) -> bool { | ||
230 | true | ||
231 | } | ||
232 | |||
233 | fn intern_generic_arg( | ||
234 | &self, | ||
235 | parameter: chalk_ir::GenericArgData<Self>, | ||
236 | ) -> chalk_ir::GenericArgData<Self> { | ||
237 | parameter | ||
238 | } | ||
239 | |||
240 | fn generic_arg_data<'a>( | ||
241 | &self, | ||
242 | parameter: &'a chalk_ir::GenericArgData<Self>, | ||
243 | ) -> &'a chalk_ir::GenericArgData<Self> { | ||
244 | parameter | ||
245 | } | ||
246 | |||
247 | fn intern_goal(&self, goal: GoalData<Self>) -> Arc<GoalData<Self>> { | ||
248 | Arc::new(goal) | ||
249 | } | ||
250 | |||
251 | fn intern_goals<E>( | ||
252 | &self, | ||
253 | data: impl IntoIterator<Item = Result<Goal<Self>, E>>, | ||
254 | ) -> Result<Self::InternedGoals, E> { | ||
255 | data.into_iter().collect() | ||
256 | } | ||
257 | |||
258 | fn goal_data<'a>(&self, goal: &'a Arc<GoalData<Self>>) -> &'a GoalData<Self> { | ||
259 | goal | ||
260 | } | ||
261 | |||
262 | fn goals_data<'a>(&self, goals: &'a Vec<Goal<Interner>>) -> &'a [Goal<Interner>] { | ||
263 | goals | ||
264 | } | ||
265 | |||
266 | fn intern_substitution<E>( | ||
267 | &self, | ||
268 | data: impl IntoIterator<Item = Result<GenericArg<Self>, E>>, | ||
269 | ) -> Result<Vec<GenericArg<Self>>, E> { | ||
270 | data.into_iter().collect() | ||
271 | } | ||
272 | |||
273 | fn substitution_data<'a>( | ||
274 | &self, | ||
275 | substitution: &'a Vec<GenericArg<Self>>, | ||
276 | ) -> &'a [GenericArg<Self>] { | ||
277 | substitution | ||
278 | } | ||
279 | |||
280 | fn intern_program_clause( | ||
281 | &self, | ||
282 | data: chalk_ir::ProgramClauseData<Self>, | ||
283 | ) -> chalk_ir::ProgramClauseData<Self> { | ||
284 | data | ||
285 | } | ||
286 | |||
287 | fn program_clause_data<'a>( | ||
288 | &self, | ||
289 | clause: &'a chalk_ir::ProgramClauseData<Self>, | ||
290 | ) -> &'a chalk_ir::ProgramClauseData<Self> { | ||
291 | clause | ||
292 | } | ||
293 | |||
294 | fn intern_program_clauses<E>( | ||
295 | &self, | ||
296 | data: impl IntoIterator<Item = Result<chalk_ir::ProgramClause<Self>, E>>, | ||
297 | ) -> Result<Arc<[chalk_ir::ProgramClause<Self>]>, E> { | ||
298 | data.into_iter().collect() | ||
299 | } | ||
300 | |||
301 | fn program_clauses_data<'a>( | ||
302 | &self, | ||
303 | clauses: &'a Arc<[chalk_ir::ProgramClause<Self>]>, | ||
304 | ) -> &'a [chalk_ir::ProgramClause<Self>] { | ||
305 | &clauses | ||
306 | } | ||
307 | |||
308 | fn intern_quantified_where_clauses<E>( | ||
309 | &self, | ||
310 | data: impl IntoIterator<Item = Result<chalk_ir::QuantifiedWhereClause<Self>, E>>, | ||
311 | ) -> Result<Self::InternedQuantifiedWhereClauses, E> { | ||
312 | data.into_iter().collect() | ||
313 | } | ||
314 | |||
315 | fn quantified_where_clauses_data<'a>( | ||
316 | &self, | ||
317 | clauses: &'a Self::InternedQuantifiedWhereClauses, | ||
318 | ) -> &'a [chalk_ir::QuantifiedWhereClause<Self>] { | ||
319 | clauses | ||
320 | } | ||
321 | |||
322 | fn intern_generic_arg_kinds<E>( | ||
323 | &self, | ||
324 | data: impl IntoIterator<Item = Result<chalk_ir::VariableKind<Self>, E>>, | ||
325 | ) -> Result<Self::InternedVariableKinds, E> { | ||
326 | data.into_iter().collect() | ||
327 | } | ||
328 | |||
329 | fn variable_kinds_data<'a>( | ||
330 | &self, | ||
331 | parameter_kinds: &'a Self::InternedVariableKinds, | ||
332 | ) -> &'a [chalk_ir::VariableKind<Self>] { | ||
333 | ¶meter_kinds | ||
334 | } | ||
335 | |||
336 | fn intern_canonical_var_kinds<E>( | ||
337 | &self, | ||
338 | data: impl IntoIterator<Item = Result<chalk_ir::CanonicalVarKind<Self>, E>>, | ||
339 | ) -> Result<Self::InternedCanonicalVarKinds, E> { | ||
340 | data.into_iter().collect() | ||
341 | } | ||
342 | |||
343 | fn canonical_var_kinds_data<'a>( | ||
344 | &self, | ||
345 | canonical_var_kinds: &'a Self::InternedCanonicalVarKinds, | ||
346 | ) -> &'a [chalk_ir::CanonicalVarKind<Self>] { | ||
347 | &canonical_var_kinds | ||
348 | } | ||
349 | } | ||
350 | |||
351 | impl chalk_ir::interner::HasInterner for Interner { | ||
352 | type Interner = Self; | ||
353 | } | ||
diff --git a/crates/ra_hir_ty/src/traits/chalk/mapping.rs b/crates/ra_hir_ty/src/traits/chalk/mapping.rs new file mode 100644 index 000000000..7082cb095 --- /dev/null +++ b/crates/ra_hir_ty/src/traits/chalk/mapping.rs | |||
@@ -0,0 +1,701 @@ | |||
1 | //! This module contains the implementations of the `ToChalk` trait, which | ||
2 | //! handles conversion between our data types and their corresponding types in | ||
3 | //! Chalk (in both directions); plus some helper functions for more specialized | ||
4 | //! conversions. | ||
5 | |||
6 | use chalk_ir::{ | ||
7 | cast::Cast, fold::shift::Shift, interner::HasInterner, PlaceholderIndex, Scalar, TypeName, | ||
8 | UniverseIndex, | ||
9 | }; | ||
10 | |||
11 | use hir_def::{type_ref::Mutability, AssocContainerId, GenericDefId, Lookup, TypeAliasId}; | ||
12 | use ra_db::salsa::InternKey; | ||
13 | |||
14 | use crate::{ | ||
15 | db::HirDatabase, | ||
16 | primitive::{FloatBitness, FloatTy, IntBitness, IntTy, Signedness, Uncertain}, | ||
17 | traits::{builtin, AssocTyValue, Canonical, Impl, Obligation}, | ||
18 | ApplicationTy, CallableDef, GenericPredicate, InEnvironment, ProjectionPredicate, ProjectionTy, | ||
19 | Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, | ||
20 | }; | ||
21 | |||
22 | use super::interner::*; | ||
23 | use super::*; | ||
24 | |||
25 | impl ToChalk for Ty { | ||
26 | type Chalk = chalk_ir::Ty<Interner>; | ||
27 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::Ty<Interner> { | ||
28 | match self { | ||
29 | Ty::Apply(apply_ty) => match apply_ty.ctor { | ||
30 | TypeCtor::Ref(m) => ref_to_chalk(db, m, apply_ty.parameters), | ||
31 | TypeCtor::FnPtr { num_args: _ } => { | ||
32 | let substitution = apply_ty.parameters.to_chalk(db).shifted_in(&Interner); | ||
33 | chalk_ir::TyData::Function(chalk_ir::Fn { num_binders: 0, substitution }) | ||
34 | .intern(&Interner) | ||
35 | } | ||
36 | _ => { | ||
37 | let name = apply_ty.ctor.to_chalk(db); | ||
38 | let substitution = apply_ty.parameters.to_chalk(db); | ||
39 | chalk_ir::ApplicationTy { name, substitution }.cast(&Interner).intern(&Interner) | ||
40 | } | ||
41 | }, | ||
42 | Ty::Projection(proj_ty) => { | ||
43 | let associated_ty_id = proj_ty.associated_ty.to_chalk(db); | ||
44 | let substitution = proj_ty.parameters.to_chalk(db); | ||
45 | chalk_ir::AliasTy::Projection(chalk_ir::ProjectionTy { | ||
46 | associated_ty_id, | ||
47 | substitution, | ||
48 | }) | ||
49 | .cast(&Interner) | ||
50 | .intern(&Interner) | ||
51 | } | ||
52 | Ty::Placeholder(id) => { | ||
53 | let interned_id = db.intern_type_param_id(id); | ||
54 | PlaceholderIndex { | ||
55 | ui: UniverseIndex::ROOT, | ||
56 | idx: interned_id.as_intern_id().as_usize(), | ||
57 | } | ||
58 | .to_ty::<Interner>(&Interner) | ||
59 | } | ||
60 | Ty::Bound(idx) => chalk_ir::TyData::BoundVar(idx).intern(&Interner), | ||
61 | Ty::Infer(_infer_ty) => panic!("uncanonicalized infer ty"), | ||
62 | Ty::Dyn(predicates) => { | ||
63 | let where_clauses = chalk_ir::QuantifiedWhereClauses::from( | ||
64 | &Interner, | ||
65 | predicates.iter().filter(|p| !p.is_error()).cloned().map(|p| p.to_chalk(db)), | ||
66 | ); | ||
67 | let bounded_ty = chalk_ir::DynTy { bounds: make_binders(where_clauses, 1) }; | ||
68 | chalk_ir::TyData::Dyn(bounded_ty).intern(&Interner) | ||
69 | } | ||
70 | Ty::Opaque(_) | Ty::Unknown => { | ||
71 | let substitution = chalk_ir::Substitution::empty(&Interner); | ||
72 | let name = TypeName::Error; | ||
73 | chalk_ir::ApplicationTy { name, substitution }.cast(&Interner).intern(&Interner) | ||
74 | } | ||
75 | } | ||
76 | } | ||
77 | fn from_chalk(db: &dyn HirDatabase, chalk: chalk_ir::Ty<Interner>) -> Self { | ||
78 | match chalk.data(&Interner).clone() { | ||
79 | chalk_ir::TyData::Apply(apply_ty) => match apply_ty.name { | ||
80 | TypeName::Error => Ty::Unknown, | ||
81 | TypeName::Ref(m) => ref_from_chalk(db, m, apply_ty.substitution), | ||
82 | _ => { | ||
83 | let ctor = from_chalk(db, apply_ty.name); | ||
84 | let parameters = from_chalk(db, apply_ty.substitution); | ||
85 | Ty::Apply(ApplicationTy { ctor, parameters }) | ||
86 | } | ||
87 | }, | ||
88 | chalk_ir::TyData::Placeholder(idx) => { | ||
89 | assert_eq!(idx.ui, UniverseIndex::ROOT); | ||
90 | let interned_id = crate::db::GlobalTypeParamId::from_intern_id( | ||
91 | crate::salsa::InternId::from(idx.idx), | ||
92 | ); | ||
93 | Ty::Placeholder(db.lookup_intern_type_param_id(interned_id)) | ||
94 | } | ||
95 | chalk_ir::TyData::Alias(chalk_ir::AliasTy::Projection(proj)) => { | ||
96 | let associated_ty = from_chalk(db, proj.associated_ty_id); | ||
97 | let parameters = from_chalk(db, proj.substitution); | ||
98 | Ty::Projection(ProjectionTy { associated_ty, parameters }) | ||
99 | } | ||
100 | chalk_ir::TyData::Alias(chalk_ir::AliasTy::Opaque(_)) => unimplemented!(), | ||
101 | chalk_ir::TyData::Function(chalk_ir::Fn { num_binders: _, substitution }) => { | ||
102 | let parameters: Substs = from_chalk(db, substitution); | ||
103 | Ty::Apply(ApplicationTy { | ||
104 | ctor: TypeCtor::FnPtr { num_args: (parameters.len() - 1) as u16 }, | ||
105 | parameters, | ||
106 | }) | ||
107 | } | ||
108 | chalk_ir::TyData::BoundVar(idx) => Ty::Bound(idx), | ||
109 | chalk_ir::TyData::InferenceVar(_iv) => Ty::Unknown, | ||
110 | chalk_ir::TyData::Dyn(where_clauses) => { | ||
111 | assert_eq!(where_clauses.bounds.binders.len(&Interner), 1); | ||
112 | let predicates = where_clauses | ||
113 | .bounds | ||
114 | .skip_binders() | ||
115 | .iter(&Interner) | ||
116 | .map(|c| from_chalk(db, c.clone())) | ||
117 | .collect(); | ||
118 | Ty::Dyn(predicates) | ||
119 | } | ||
120 | } | ||
121 | } | ||
122 | } | ||
123 | |||
124 | const LIFETIME_PLACEHOLDER: PlaceholderIndex = | ||
125 | PlaceholderIndex { ui: UniverseIndex::ROOT, idx: usize::MAX }; | ||
126 | |||
127 | /// We currently don't model lifetimes, but Chalk does. So, we have to insert a | ||
128 | /// fake lifetime here, because Chalks built-in logic may expect it to be there. | ||
129 | fn ref_to_chalk( | ||
130 | db: &dyn HirDatabase, | ||
131 | mutability: Mutability, | ||
132 | subst: Substs, | ||
133 | ) -> chalk_ir::Ty<Interner> { | ||
134 | let arg = subst[0].clone().to_chalk(db); | ||
135 | let lifetime = LIFETIME_PLACEHOLDER.to_lifetime(&Interner); | ||
136 | chalk_ir::ApplicationTy { | ||
137 | name: TypeName::Ref(mutability.to_chalk(db)), | ||
138 | substitution: chalk_ir::Substitution::from( | ||
139 | &Interner, | ||
140 | vec![lifetime.cast(&Interner), arg.cast(&Interner)], | ||
141 | ), | ||
142 | } | ||
143 | .intern(&Interner) | ||
144 | } | ||
145 | |||
146 | /// Here we remove the lifetime from the type we got from Chalk. | ||
147 | fn ref_from_chalk( | ||
148 | db: &dyn HirDatabase, | ||
149 | mutability: chalk_ir::Mutability, | ||
150 | subst: chalk_ir::Substitution<Interner>, | ||
151 | ) -> Ty { | ||
152 | let tys = subst | ||
153 | .iter(&Interner) | ||
154 | .filter_map(|p| Some(from_chalk(db, p.ty(&Interner)?.clone()))) | ||
155 | .collect(); | ||
156 | Ty::apply(TypeCtor::Ref(from_chalk(db, mutability)), Substs(tys)) | ||
157 | } | ||
158 | |||
159 | impl ToChalk for Substs { | ||
160 | type Chalk = chalk_ir::Substitution<Interner>; | ||
161 | |||
162 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::Substitution<Interner> { | ||
163 | chalk_ir::Substitution::from(&Interner, self.iter().map(|ty| ty.clone().to_chalk(db))) | ||
164 | } | ||
165 | |||
166 | fn from_chalk(db: &dyn HirDatabase, parameters: chalk_ir::Substitution<Interner>) -> Substs { | ||
167 | let tys = parameters | ||
168 | .iter(&Interner) | ||
169 | .map(|p| match p.ty(&Interner) { | ||
170 | Some(ty) => from_chalk(db, ty.clone()), | ||
171 | None => unimplemented!(), | ||
172 | }) | ||
173 | .collect(); | ||
174 | Substs(tys) | ||
175 | } | ||
176 | } | ||
177 | |||
178 | impl ToChalk for TraitRef { | ||
179 | type Chalk = chalk_ir::TraitRef<Interner>; | ||
180 | |||
181 | fn to_chalk(self: TraitRef, db: &dyn HirDatabase) -> chalk_ir::TraitRef<Interner> { | ||
182 | let trait_id = self.trait_.to_chalk(db); | ||
183 | let substitution = self.substs.to_chalk(db); | ||
184 | chalk_ir::TraitRef { trait_id, substitution } | ||
185 | } | ||
186 | |||
187 | fn from_chalk(db: &dyn HirDatabase, trait_ref: chalk_ir::TraitRef<Interner>) -> Self { | ||
188 | let trait_ = from_chalk(db, trait_ref.trait_id); | ||
189 | let substs = from_chalk(db, trait_ref.substitution); | ||
190 | TraitRef { trait_, substs } | ||
191 | } | ||
192 | } | ||
193 | |||
194 | impl ToChalk for hir_def::TraitId { | ||
195 | type Chalk = TraitId; | ||
196 | |||
197 | fn to_chalk(self, _db: &dyn HirDatabase) -> TraitId { | ||
198 | chalk_ir::TraitId(self.as_intern_id()) | ||
199 | } | ||
200 | |||
201 | fn from_chalk(_db: &dyn HirDatabase, trait_id: TraitId) -> hir_def::TraitId { | ||
202 | InternKey::from_intern_id(trait_id.0) | ||
203 | } | ||
204 | } | ||
205 | |||
206 | impl ToChalk for TypeCtor { | ||
207 | type Chalk = TypeName<Interner>; | ||
208 | |||
209 | fn to_chalk(self, db: &dyn HirDatabase) -> TypeName<Interner> { | ||
210 | match self { | ||
211 | TypeCtor::AssociatedType(type_alias) => { | ||
212 | let type_id = type_alias.to_chalk(db); | ||
213 | TypeName::AssociatedType(type_id) | ||
214 | } | ||
215 | |||
216 | TypeCtor::Bool => TypeName::Scalar(Scalar::Bool), | ||
217 | TypeCtor::Char => TypeName::Scalar(Scalar::Char), | ||
218 | TypeCtor::Int(Uncertain::Known(int_ty)) => TypeName::Scalar(int_ty_to_chalk(int_ty)), | ||
219 | TypeCtor::Float(Uncertain::Known(FloatTy { bitness: FloatBitness::X32 })) => { | ||
220 | TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F32)) | ||
221 | } | ||
222 | TypeCtor::Float(Uncertain::Known(FloatTy { bitness: FloatBitness::X64 })) => { | ||
223 | TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F64)) | ||
224 | } | ||
225 | |||
226 | TypeCtor::Tuple { cardinality } => TypeName::Tuple(cardinality.into()), | ||
227 | TypeCtor::RawPtr(mutability) => TypeName::Raw(mutability.to_chalk(db)), | ||
228 | TypeCtor::Slice => TypeName::Slice, | ||
229 | TypeCtor::Ref(mutability) => TypeName::Ref(mutability.to_chalk(db)), | ||
230 | TypeCtor::Str => TypeName::Str, | ||
231 | TypeCtor::FnDef(callable_def) => { | ||
232 | let id = callable_def.to_chalk(db); | ||
233 | TypeName::FnDef(id) | ||
234 | } | ||
235 | TypeCtor::Int(Uncertain::Unknown) | ||
236 | | TypeCtor::Float(Uncertain::Unknown) | ||
237 | | TypeCtor::Adt(_) | ||
238 | | TypeCtor::Array | ||
239 | | TypeCtor::FnPtr { .. } | ||
240 | | TypeCtor::Never | ||
241 | | TypeCtor::Closure { .. } => { | ||
242 | // other TypeCtors get interned and turned into a chalk StructId | ||
243 | let struct_id = db.intern_type_ctor(self).into(); | ||
244 | TypeName::Adt(struct_id) | ||
245 | } | ||
246 | } | ||
247 | } | ||
248 | |||
249 | fn from_chalk(db: &dyn HirDatabase, type_name: TypeName<Interner>) -> TypeCtor { | ||
250 | match type_name { | ||
251 | TypeName::Adt(struct_id) => db.lookup_intern_type_ctor(struct_id.into()), | ||
252 | TypeName::AssociatedType(type_id) => TypeCtor::AssociatedType(from_chalk(db, type_id)), | ||
253 | TypeName::OpaqueType(_) => unreachable!(), | ||
254 | |||
255 | TypeName::Scalar(Scalar::Bool) => TypeCtor::Bool, | ||
256 | TypeName::Scalar(Scalar::Char) => TypeCtor::Char, | ||
257 | TypeName::Scalar(Scalar::Int(int_ty)) => TypeCtor::Int(Uncertain::Known(IntTy { | ||
258 | signedness: Signedness::Signed, | ||
259 | bitness: bitness_from_chalk_int(int_ty), | ||
260 | })), | ||
261 | TypeName::Scalar(Scalar::Uint(uint_ty)) => TypeCtor::Int(Uncertain::Known(IntTy { | ||
262 | signedness: Signedness::Unsigned, | ||
263 | bitness: bitness_from_chalk_uint(uint_ty), | ||
264 | })), | ||
265 | TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F32)) => { | ||
266 | TypeCtor::Float(Uncertain::Known(FloatTy { bitness: FloatBitness::X32 })) | ||
267 | } | ||
268 | TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F64)) => { | ||
269 | TypeCtor::Float(Uncertain::Known(FloatTy { bitness: FloatBitness::X64 })) | ||
270 | } | ||
271 | TypeName::Tuple(cardinality) => TypeCtor::Tuple { cardinality: cardinality as u16 }, | ||
272 | TypeName::Raw(mutability) => TypeCtor::RawPtr(from_chalk(db, mutability)), | ||
273 | TypeName::Slice => TypeCtor::Slice, | ||
274 | TypeName::Ref(mutability) => TypeCtor::Ref(from_chalk(db, mutability)), | ||
275 | TypeName::Str => TypeCtor::Str, | ||
276 | |||
277 | TypeName::FnDef(fn_def_id) => { | ||
278 | let callable_def = from_chalk(db, fn_def_id); | ||
279 | TypeCtor::FnDef(callable_def) | ||
280 | } | ||
281 | |||
282 | TypeName::Error => { | ||
283 | // this should not be reached, since we don't represent TypeName::Error with TypeCtor | ||
284 | unreachable!() | ||
285 | } | ||
286 | } | ||
287 | } | ||
288 | } | ||
289 | |||
290 | fn bitness_from_chalk_uint(uint_ty: chalk_ir::UintTy) -> IntBitness { | ||
291 | use chalk_ir::UintTy; | ||
292 | |||
293 | match uint_ty { | ||
294 | UintTy::Usize => IntBitness::Xsize, | ||
295 | UintTy::U8 => IntBitness::X8, | ||
296 | UintTy::U16 => IntBitness::X16, | ||
297 | UintTy::U32 => IntBitness::X32, | ||
298 | UintTy::U64 => IntBitness::X64, | ||
299 | UintTy::U128 => IntBitness::X128, | ||
300 | } | ||
301 | } | ||
302 | |||
303 | fn bitness_from_chalk_int(int_ty: chalk_ir::IntTy) -> IntBitness { | ||
304 | use chalk_ir::IntTy; | ||
305 | |||
306 | match int_ty { | ||
307 | IntTy::Isize => IntBitness::Xsize, | ||
308 | IntTy::I8 => IntBitness::X8, | ||
309 | IntTy::I16 => IntBitness::X16, | ||
310 | IntTy::I32 => IntBitness::X32, | ||
311 | IntTy::I64 => IntBitness::X64, | ||
312 | IntTy::I128 => IntBitness::X128, | ||
313 | } | ||
314 | } | ||
315 | |||
316 | fn int_ty_to_chalk(int_ty: IntTy) -> Scalar { | ||
317 | use chalk_ir::{IntTy, UintTy}; | ||
318 | |||
319 | match int_ty.signedness { | ||
320 | Signedness::Signed => Scalar::Int(match int_ty.bitness { | ||
321 | IntBitness::Xsize => IntTy::Isize, | ||
322 | IntBitness::X8 => IntTy::I8, | ||
323 | IntBitness::X16 => IntTy::I16, | ||
324 | IntBitness::X32 => IntTy::I32, | ||
325 | IntBitness::X64 => IntTy::I64, | ||
326 | IntBitness::X128 => IntTy::I128, | ||
327 | }), | ||
328 | Signedness::Unsigned => Scalar::Uint(match int_ty.bitness { | ||
329 | IntBitness::Xsize => UintTy::Usize, | ||
330 | IntBitness::X8 => UintTy::U8, | ||
331 | IntBitness::X16 => UintTy::U16, | ||
332 | IntBitness::X32 => UintTy::U32, | ||
333 | IntBitness::X64 => UintTy::U64, | ||
334 | IntBitness::X128 => UintTy::U128, | ||
335 | }), | ||
336 | } | ||
337 | } | ||
338 | |||
339 | impl ToChalk for Mutability { | ||
340 | type Chalk = chalk_ir::Mutability; | ||
341 | fn to_chalk(self, _db: &dyn HirDatabase) -> Self::Chalk { | ||
342 | match self { | ||
343 | Mutability::Shared => chalk_ir::Mutability::Not, | ||
344 | Mutability::Mut => chalk_ir::Mutability::Mut, | ||
345 | } | ||
346 | } | ||
347 | fn from_chalk(_db: &dyn HirDatabase, chalk: Self::Chalk) -> Self { | ||
348 | match chalk { | ||
349 | chalk_ir::Mutability::Mut => Mutability::Mut, | ||
350 | chalk_ir::Mutability::Not => Mutability::Shared, | ||
351 | } | ||
352 | } | ||
353 | } | ||
354 | |||
355 | impl ToChalk for Impl { | ||
356 | type Chalk = ImplId; | ||
357 | |||
358 | fn to_chalk(self, db: &dyn HirDatabase) -> ImplId { | ||
359 | db.intern_chalk_impl(self).into() | ||
360 | } | ||
361 | |||
362 | fn from_chalk(db: &dyn HirDatabase, impl_id: ImplId) -> Impl { | ||
363 | db.lookup_intern_chalk_impl(impl_id.into()) | ||
364 | } | ||
365 | } | ||
366 | |||
367 | impl ToChalk for CallableDef { | ||
368 | type Chalk = FnDefId; | ||
369 | |||
370 | fn to_chalk(self, db: &dyn HirDatabase) -> FnDefId { | ||
371 | db.intern_callable_def(self).into() | ||
372 | } | ||
373 | |||
374 | fn from_chalk(db: &dyn HirDatabase, fn_def_id: FnDefId) -> CallableDef { | ||
375 | db.lookup_intern_callable_def(fn_def_id.into()) | ||
376 | } | ||
377 | } | ||
378 | |||
379 | impl ToChalk for TypeAliasId { | ||
380 | type Chalk = AssocTypeId; | ||
381 | |||
382 | fn to_chalk(self, _db: &dyn HirDatabase) -> AssocTypeId { | ||
383 | chalk_ir::AssocTypeId(self.as_intern_id()) | ||
384 | } | ||
385 | |||
386 | fn from_chalk(_db: &dyn HirDatabase, type_alias_id: AssocTypeId) -> TypeAliasId { | ||
387 | InternKey::from_intern_id(type_alias_id.0) | ||
388 | } | ||
389 | } | ||
390 | |||
391 | impl ToChalk for AssocTyValue { | ||
392 | type Chalk = AssociatedTyValueId; | ||
393 | |||
394 | fn to_chalk(self, db: &dyn HirDatabase) -> AssociatedTyValueId { | ||
395 | db.intern_assoc_ty_value(self).into() | ||
396 | } | ||
397 | |||
398 | fn from_chalk(db: &dyn HirDatabase, assoc_ty_value_id: AssociatedTyValueId) -> AssocTyValue { | ||
399 | db.lookup_intern_assoc_ty_value(assoc_ty_value_id.into()) | ||
400 | } | ||
401 | } | ||
402 | |||
403 | impl ToChalk for GenericPredicate { | ||
404 | type Chalk = chalk_ir::QuantifiedWhereClause<Interner>; | ||
405 | |||
406 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::QuantifiedWhereClause<Interner> { | ||
407 | match self { | ||
408 | GenericPredicate::Implemented(trait_ref) => { | ||
409 | let chalk_trait_ref = trait_ref.to_chalk(db); | ||
410 | let chalk_trait_ref = chalk_trait_ref.shifted_in(&Interner); | ||
411 | make_binders(chalk_ir::WhereClause::Implemented(chalk_trait_ref), 0) | ||
412 | } | ||
413 | GenericPredicate::Projection(projection_pred) => { | ||
414 | let ty = projection_pred.ty.to_chalk(db).shifted_in(&Interner); | ||
415 | let projection = projection_pred.projection_ty.to_chalk(db).shifted_in(&Interner); | ||
416 | let alias = chalk_ir::AliasTy::Projection(projection); | ||
417 | make_binders(chalk_ir::WhereClause::AliasEq(chalk_ir::AliasEq { alias, ty }), 0) | ||
418 | } | ||
419 | GenericPredicate::Error => panic!("tried passing GenericPredicate::Error to Chalk"), | ||
420 | } | ||
421 | } | ||
422 | |||
423 | fn from_chalk( | ||
424 | db: &dyn HirDatabase, | ||
425 | where_clause: chalk_ir::QuantifiedWhereClause<Interner>, | ||
426 | ) -> GenericPredicate { | ||
427 | // we don't produce any where clauses with binders and can't currently deal with them | ||
428 | match where_clause | ||
429 | .skip_binders() | ||
430 | .shifted_out(&Interner) | ||
431 | .expect("unexpected bound vars in where clause") | ||
432 | { | ||
433 | chalk_ir::WhereClause::Implemented(tr) => { | ||
434 | GenericPredicate::Implemented(from_chalk(db, tr)) | ||
435 | } | ||
436 | chalk_ir::WhereClause::AliasEq(projection_eq) => { | ||
437 | let projection_ty = from_chalk( | ||
438 | db, | ||
439 | match projection_eq.alias { | ||
440 | chalk_ir::AliasTy::Projection(p) => p, | ||
441 | _ => unimplemented!(), | ||
442 | }, | ||
443 | ); | ||
444 | let ty = from_chalk(db, projection_eq.ty); | ||
445 | GenericPredicate::Projection(ProjectionPredicate { projection_ty, ty }) | ||
446 | } | ||
447 | } | ||
448 | } | ||
449 | } | ||
450 | |||
451 | impl ToChalk for ProjectionTy { | ||
452 | type Chalk = chalk_ir::ProjectionTy<Interner>; | ||
453 | |||
454 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::ProjectionTy<Interner> { | ||
455 | chalk_ir::ProjectionTy { | ||
456 | associated_ty_id: self.associated_ty.to_chalk(db), | ||
457 | substitution: self.parameters.to_chalk(db), | ||
458 | } | ||
459 | } | ||
460 | |||
461 | fn from_chalk( | ||
462 | db: &dyn HirDatabase, | ||
463 | projection_ty: chalk_ir::ProjectionTy<Interner>, | ||
464 | ) -> ProjectionTy { | ||
465 | ProjectionTy { | ||
466 | associated_ty: from_chalk(db, projection_ty.associated_ty_id), | ||
467 | parameters: from_chalk(db, projection_ty.substitution), | ||
468 | } | ||
469 | } | ||
470 | } | ||
471 | |||
472 | impl ToChalk for ProjectionPredicate { | ||
473 | type Chalk = chalk_ir::AliasEq<Interner>; | ||
474 | |||
475 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::AliasEq<Interner> { | ||
476 | chalk_ir::AliasEq { | ||
477 | alias: chalk_ir::AliasTy::Projection(self.projection_ty.to_chalk(db)), | ||
478 | ty: self.ty.to_chalk(db), | ||
479 | } | ||
480 | } | ||
481 | |||
482 | fn from_chalk(_db: &dyn HirDatabase, _normalize: chalk_ir::AliasEq<Interner>) -> Self { | ||
483 | unimplemented!() | ||
484 | } | ||
485 | } | ||
486 | |||
487 | impl ToChalk for Obligation { | ||
488 | type Chalk = chalk_ir::DomainGoal<Interner>; | ||
489 | |||
490 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::DomainGoal<Interner> { | ||
491 | match self { | ||
492 | Obligation::Trait(tr) => tr.to_chalk(db).cast(&Interner), | ||
493 | Obligation::Projection(pr) => pr.to_chalk(db).cast(&Interner), | ||
494 | } | ||
495 | } | ||
496 | |||
497 | fn from_chalk(_db: &dyn HirDatabase, _goal: chalk_ir::DomainGoal<Interner>) -> Self { | ||
498 | unimplemented!() | ||
499 | } | ||
500 | } | ||
501 | |||
502 | impl<T> ToChalk for Canonical<T> | ||
503 | where | ||
504 | T: ToChalk, | ||
505 | T::Chalk: HasInterner<Interner = Interner>, | ||
506 | { | ||
507 | type Chalk = chalk_ir::Canonical<T::Chalk>; | ||
508 | |||
509 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::Canonical<T::Chalk> { | ||
510 | let parameter = chalk_ir::CanonicalVarKind::new( | ||
511 | chalk_ir::VariableKind::Ty, | ||
512 | chalk_ir::UniverseIndex::ROOT, | ||
513 | ); | ||
514 | let value = self.value.to_chalk(db); | ||
515 | chalk_ir::Canonical { | ||
516 | value, | ||
517 | binders: chalk_ir::CanonicalVarKinds::from(&Interner, vec![parameter; self.num_vars]), | ||
518 | } | ||
519 | } | ||
520 | |||
521 | fn from_chalk(db: &dyn HirDatabase, canonical: chalk_ir::Canonical<T::Chalk>) -> Canonical<T> { | ||
522 | Canonical { | ||
523 | num_vars: canonical.binders.len(&Interner), | ||
524 | value: from_chalk(db, canonical.value), | ||
525 | } | ||
526 | } | ||
527 | } | ||
528 | |||
529 | impl ToChalk for Arc<TraitEnvironment> { | ||
530 | type Chalk = chalk_ir::Environment<Interner>; | ||
531 | |||
532 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::Environment<Interner> { | ||
533 | let mut clauses = Vec::new(); | ||
534 | for pred in &self.predicates { | ||
535 | if pred.is_error() { | ||
536 | // for env, we just ignore errors | ||
537 | continue; | ||
538 | } | ||
539 | let program_clause: chalk_ir::ProgramClause<Interner> = | ||
540 | pred.clone().to_chalk(db).cast(&Interner); | ||
541 | clauses.push(program_clause.into_from_env_clause(&Interner)); | ||
542 | } | ||
543 | chalk_ir::Environment::new(&Interner).add_clauses(&Interner, clauses) | ||
544 | } | ||
545 | |||
546 | fn from_chalk( | ||
547 | _db: &dyn HirDatabase, | ||
548 | _env: chalk_ir::Environment<Interner>, | ||
549 | ) -> Arc<TraitEnvironment> { | ||
550 | unimplemented!() | ||
551 | } | ||
552 | } | ||
553 | |||
554 | impl<T: ToChalk> ToChalk for InEnvironment<T> | ||
555 | where | ||
556 | T::Chalk: chalk_ir::interner::HasInterner<Interner = Interner>, | ||
557 | { | ||
558 | type Chalk = chalk_ir::InEnvironment<T::Chalk>; | ||
559 | |||
560 | fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::InEnvironment<T::Chalk> { | ||
561 | chalk_ir::InEnvironment { | ||
562 | environment: self.environment.to_chalk(db), | ||
563 | goal: self.value.to_chalk(db), | ||
564 | } | ||
565 | } | ||
566 | |||
567 | fn from_chalk( | ||
568 | db: &dyn HirDatabase, | ||
569 | in_env: chalk_ir::InEnvironment<T::Chalk>, | ||
570 | ) -> InEnvironment<T> { | ||
571 | InEnvironment { | ||
572 | environment: from_chalk(db, in_env.environment), | ||
573 | value: from_chalk(db, in_env.goal), | ||
574 | } | ||
575 | } | ||
576 | } | ||
577 | |||
578 | impl ToChalk for builtin::BuiltinImplData { | ||
579 | type Chalk = ImplDatum; | ||
580 | |||
581 | fn to_chalk(self, db: &dyn HirDatabase) -> ImplDatum { | ||
582 | let impl_type = chalk_rust_ir::ImplType::External; | ||
583 | let where_clauses = self.where_clauses.into_iter().map(|w| w.to_chalk(db)).collect(); | ||
584 | |||
585 | let impl_datum_bound = | ||
586 | chalk_rust_ir::ImplDatumBound { trait_ref: self.trait_ref.to_chalk(db), where_clauses }; | ||
587 | let associated_ty_value_ids = | ||
588 | self.assoc_ty_values.into_iter().map(|v| v.to_chalk(db)).collect(); | ||
589 | chalk_rust_ir::ImplDatum { | ||
590 | binders: make_binders(impl_datum_bound, self.num_vars), | ||
591 | impl_type, | ||
592 | polarity: chalk_rust_ir::Polarity::Positive, | ||
593 | associated_ty_value_ids, | ||
594 | } | ||
595 | } | ||
596 | |||
597 | fn from_chalk(_db: &dyn HirDatabase, _data: ImplDatum) -> Self { | ||
598 | unimplemented!() | ||
599 | } | ||
600 | } | ||
601 | |||
602 | impl ToChalk for builtin::BuiltinImplAssocTyValueData { | ||
603 | type Chalk = AssociatedTyValue; | ||
604 | |||
605 | fn to_chalk(self, db: &dyn HirDatabase) -> AssociatedTyValue { | ||
606 | let ty = self.value.to_chalk(db); | ||
607 | let value_bound = chalk_rust_ir::AssociatedTyValueBound { ty }; | ||
608 | |||
609 | chalk_rust_ir::AssociatedTyValue { | ||
610 | associated_ty_id: self.assoc_ty_id.to_chalk(db), | ||
611 | impl_id: self.impl_.to_chalk(db), | ||
612 | value: make_binders(value_bound, self.num_vars), | ||
613 | } | ||
614 | } | ||
615 | |||
616 | fn from_chalk( | ||
617 | _db: &dyn HirDatabase, | ||
618 | _data: AssociatedTyValue, | ||
619 | ) -> builtin::BuiltinImplAssocTyValueData { | ||
620 | unimplemented!() | ||
621 | } | ||
622 | } | ||
623 | |||
624 | pub(super) fn make_binders<T>(value: T, num_vars: usize) -> chalk_ir::Binders<T> | ||
625 | where | ||
626 | T: HasInterner<Interner = Interner>, | ||
627 | { | ||
628 | chalk_ir::Binders::new( | ||
629 | chalk_ir::VariableKinds::from( | ||
630 | &Interner, | ||
631 | std::iter::repeat(chalk_ir::VariableKind::Ty).take(num_vars), | ||
632 | ), | ||
633 | value, | ||
634 | ) | ||
635 | } | ||
636 | |||
637 | pub(super) fn convert_where_clauses( | ||
638 | db: &dyn HirDatabase, | ||
639 | def: GenericDefId, | ||
640 | substs: &Substs, | ||
641 | ) -> Vec<chalk_ir::QuantifiedWhereClause<Interner>> { | ||
642 | let generic_predicates = db.generic_predicates(def); | ||
643 | let mut result = Vec::with_capacity(generic_predicates.len()); | ||
644 | for pred in generic_predicates.iter() { | ||
645 | if pred.value.is_error() { | ||
646 | // skip errored predicates completely | ||
647 | continue; | ||
648 | } | ||
649 | result.push(pred.clone().subst(substs).to_chalk(db)); | ||
650 | } | ||
651 | result | ||
652 | } | ||
653 | |||
654 | pub(super) fn generic_predicate_to_inline_bound( | ||
655 | db: &dyn HirDatabase, | ||
656 | pred: &GenericPredicate, | ||
657 | self_ty: &Ty, | ||
658 | ) -> Option<chalk_rust_ir::InlineBound<Interner>> { | ||
659 | // An InlineBound is like a GenericPredicate, except the self type is left out. | ||
660 | // We don't have a special type for this, but Chalk does. | ||
661 | match pred { | ||
662 | GenericPredicate::Implemented(trait_ref) => { | ||
663 | if &trait_ref.substs[0] != self_ty { | ||
664 | // we can only convert predicates back to type bounds if they | ||
665 | // have the expected self type | ||
666 | return None; | ||
667 | } | ||
668 | let args_no_self = trait_ref.substs[1..] | ||
669 | .iter() | ||
670 | .map(|ty| ty.clone().to_chalk(db).cast(&Interner)) | ||
671 | .collect(); | ||
672 | let trait_bound = | ||
673 | chalk_rust_ir::TraitBound { trait_id: trait_ref.trait_.to_chalk(db), args_no_self }; | ||
674 | Some(chalk_rust_ir::InlineBound::TraitBound(trait_bound)) | ||
675 | } | ||
676 | GenericPredicate::Projection(proj) => { | ||
677 | if &proj.projection_ty.parameters[0] != self_ty { | ||
678 | return None; | ||
679 | } | ||
680 | let trait_ = match proj.projection_ty.associated_ty.lookup(db.upcast()).container { | ||
681 | AssocContainerId::TraitId(t) => t, | ||
682 | _ => panic!("associated type not in trait"), | ||
683 | }; | ||
684 | let args_no_self = proj.projection_ty.parameters[1..] | ||
685 | .iter() | ||
686 | .map(|ty| ty.clone().to_chalk(db).cast(&Interner)) | ||
687 | .collect(); | ||
688 | let alias_eq_bound = chalk_rust_ir::AliasEqBound { | ||
689 | value: proj.ty.clone().to_chalk(db), | ||
690 | trait_bound: chalk_rust_ir::TraitBound { | ||
691 | trait_id: trait_.to_chalk(db), | ||
692 | args_no_self, | ||
693 | }, | ||
694 | associated_ty_id: proj.projection_ty.associated_ty.to_chalk(db), | ||
695 | parameters: Vec::new(), // FIXME we don't support generic associated types yet | ||
696 | }; | ||
697 | Some(chalk_rust_ir::InlineBound::AliasEqBound(alias_eq_bound)) | ||
698 | } | ||
699 | GenericPredicate::Error => None, | ||
700 | } | ||
701 | } | ||
diff --git a/crates/ra_hir_ty/src/traits/chalk/tls.rs b/crates/ra_hir_ty/src/traits/chalk/tls.rs index 4867cb17e..d88828c7c 100644 --- a/crates/ra_hir_ty/src/traits/chalk/tls.rs +++ b/crates/ra_hir_ty/src/traits/chalk/tls.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | //! Implementation of Chalk debug helper functions using TLS. | 1 | //! Implementation of Chalk debug helper functions using TLS. |
2 | use std::fmt; | 2 | use std::fmt; |
3 | 3 | ||
4 | use chalk_ir::{AliasTy, Goal, Goals, Lifetime, Parameter, ProgramClauseImplication, TypeName}; | 4 | use chalk_ir::{AliasTy, GenericArg, Goal, Goals, Lifetime, ProgramClauseImplication, TypeName}; |
5 | use itertools::Itertools; | 5 | use itertools::Itertools; |
6 | 6 | ||
7 | use super::{from_chalk, Interner}; | 7 | use super::{from_chalk, Interner}; |
@@ -15,10 +15,10 @@ pub struct DebugContext<'a>(&'a (dyn HirDatabase + 'a)); | |||
15 | impl DebugContext<'_> { | 15 | impl DebugContext<'_> { |
16 | pub fn debug_struct_id( | 16 | pub fn debug_struct_id( |
17 | &self, | 17 | &self, |
18 | id: super::StructId, | 18 | id: super::AdtId, |
19 | f: &mut fmt::Formatter<'_>, | 19 | f: &mut fmt::Formatter<'_>, |
20 | ) -> Result<(), fmt::Error> { | 20 | ) -> Result<(), fmt::Error> { |
21 | let type_ctor: TypeCtor = from_chalk(self.0, TypeName::Struct(id)); | 21 | let type_ctor: TypeCtor = from_chalk(self.0, TypeName::Adt(id)); |
22 | match type_ctor { | 22 | match type_ctor { |
23 | TypeCtor::Bool => write!(f, "bool")?, | 23 | TypeCtor::Bool => write!(f, "bool")?, |
24 | TypeCtor::Char => write!(f, "char")?, | 24 | TypeCtor::Char => write!(f, "char")?, |
@@ -188,9 +188,9 @@ impl DebugContext<'_> { | |||
188 | write!(fmt, "{:?}", lifetime.data(&Interner)) | 188 | write!(fmt, "{:?}", lifetime.data(&Interner)) |
189 | } | 189 | } |
190 | 190 | ||
191 | pub fn debug_parameter( | 191 | pub fn debug_generic_arg( |
192 | &self, | 192 | &self, |
193 | parameter: &Parameter<Interner>, | 193 | parameter: &GenericArg<Interner>, |
194 | fmt: &mut fmt::Formatter<'_>, | 194 | fmt: &mut fmt::Formatter<'_>, |
195 | ) -> Result<(), fmt::Error> { | 195 | ) -> Result<(), fmt::Error> { |
196 | write!(fmt, "{:?}", parameter.data(&Interner).inner_debug()) | 196 | write!(fmt, "{:?}", parameter.data(&Interner).inner_debug()) |
@@ -244,6 +244,79 @@ impl DebugContext<'_> { | |||
244 | ) -> Result<(), fmt::Error> { | 244 | ) -> Result<(), fmt::Error> { |
245 | write!(fmt, "{:?}", separator_trait_ref.debug(&Interner)) | 245 | write!(fmt, "{:?}", separator_trait_ref.debug(&Interner)) |
246 | } | 246 | } |
247 | |||
248 | pub fn debug_fn_def_id( | ||
249 | &self, | ||
250 | fn_def_id: chalk_ir::FnDefId<Interner>, | ||
251 | fmt: &mut fmt::Formatter<'_>, | ||
252 | ) -> Result<(), fmt::Error> { | ||
253 | let def: CallableDef = from_chalk(self.0, fn_def_id); | ||
254 | let name = match def { | ||
255 | CallableDef::FunctionId(ff) => self.0.function_data(ff).name.clone(), | ||
256 | CallableDef::StructId(s) => self.0.struct_data(s).name.clone(), | ||
257 | CallableDef::EnumVariantId(e) => { | ||
258 | let enum_data = self.0.enum_data(e.parent); | ||
259 | enum_data.variants[e.local_id].name.clone() | ||
260 | } | ||
261 | }; | ||
262 | match def { | ||
263 | CallableDef::FunctionId(_) => write!(fmt, "{{fn {}}}", name), | ||
264 | CallableDef::StructId(_) | CallableDef::EnumVariantId(_) => { | ||
265 | write!(fmt, "{{ctor {}}}", name) | ||
266 | } | ||
267 | } | ||
268 | } | ||
269 | |||
270 | pub fn debug_const( | ||
271 | &self, | ||
272 | _constant: &chalk_ir::Const<Interner>, | ||
273 | fmt: &mut fmt::Formatter<'_>, | ||
274 | ) -> fmt::Result { | ||
275 | write!(fmt, "const") | ||
276 | } | ||
277 | |||
278 | pub fn debug_variable_kinds( | ||
279 | &self, | ||
280 | variable_kinds: &chalk_ir::VariableKinds<Interner>, | ||
281 | fmt: &mut fmt::Formatter<'_>, | ||
282 | ) -> fmt::Result { | ||
283 | write!(fmt, "{:?}", variable_kinds.as_slice(&Interner)) | ||
284 | } | ||
285 | pub fn debug_variable_kinds_with_angles( | ||
286 | &self, | ||
287 | variable_kinds: &chalk_ir::VariableKinds<Interner>, | ||
288 | fmt: &mut fmt::Formatter<'_>, | ||
289 | ) -> fmt::Result { | ||
290 | write!(fmt, "{:?}", variable_kinds.inner_debug(&Interner)) | ||
291 | } | ||
292 | pub fn debug_canonical_var_kinds( | ||
293 | &self, | ||
294 | canonical_var_kinds: &chalk_ir::CanonicalVarKinds<Interner>, | ||
295 | fmt: &mut fmt::Formatter<'_>, | ||
296 | ) -> fmt::Result { | ||
297 | write!(fmt, "{:?}", canonical_var_kinds.as_slice(&Interner)) | ||
298 | } | ||
299 | pub fn debug_program_clause( | ||
300 | &self, | ||
301 | clause: &chalk_ir::ProgramClause<Interner>, | ||
302 | fmt: &mut fmt::Formatter<'_>, | ||
303 | ) -> fmt::Result { | ||
304 | write!(fmt, "{:?}", clause.data(&Interner)) | ||
305 | } | ||
306 | pub fn debug_program_clauses( | ||
307 | &self, | ||
308 | clauses: &chalk_ir::ProgramClauses<Interner>, | ||
309 | fmt: &mut fmt::Formatter<'_>, | ||
310 | ) -> fmt::Result { | ||
311 | write!(fmt, "{:?}", clauses.as_slice(&Interner)) | ||
312 | } | ||
313 | pub fn debug_quantified_where_clauses( | ||
314 | &self, | ||
315 | clauses: &chalk_ir::QuantifiedWhereClauses<Interner>, | ||
316 | fmt: &mut fmt::Formatter<'_>, | ||
317 | ) -> fmt::Result { | ||
318 | write!(fmt, "{:?}", clauses.as_slice(&Interner)) | ||
319 | } | ||
247 | } | 320 | } |
248 | 321 | ||
249 | mod unsafe_tls { | 322 | mod unsafe_tls { |
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs index 6021f7279..cfb7c1e38 100644 --- a/crates/ra_ide/src/completion/completion_item.rs +++ b/crates/ra_ide/src/completion/completion_item.rs | |||
@@ -63,8 +63,8 @@ impl fmt::Debug for CompletionItem { | |||
63 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | 63 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
64 | let mut s = f.debug_struct("CompletionItem"); | 64 | let mut s = f.debug_struct("CompletionItem"); |
65 | s.field("label", &self.label()).field("source_range", &self.source_range()); | 65 | s.field("label", &self.label()).field("source_range", &self.source_range()); |
66 | if self.text_edit().as_indels().len() == 1 { | 66 | if self.text_edit().len() == 1 { |
67 | let atom = &self.text_edit().as_indels()[0]; | 67 | let atom = &self.text_edit().iter().next().unwrap(); |
68 | s.field("delete", &atom.delete); | 68 | s.field("delete", &atom.delete); |
69 | s.field("insert", &atom.insert); | 69 | s.field("insert", &atom.insert); |
70 | } else { | 70 | } else { |
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index 54c2bcc09..3d83c0f71 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -21,7 +21,7 @@ use ra_syntax::{ | |||
21 | }; | 21 | }; |
22 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 22 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
23 | 23 | ||
24 | use crate::{Diagnostic, FileId, FileSystemEdit, SourceChange, SourceFileEdit}; | 24 | use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceChange, SourceFileEdit}; |
25 | 25 | ||
26 | #[derive(Debug, Copy, Clone)] | 26 | #[derive(Debug, Copy, Clone)] |
27 | pub enum Severity { | 27 | pub enum Severity { |
@@ -63,8 +63,8 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
63 | .parent() | 63 | .parent() |
64 | .unwrap_or_else(|| RelativePath::new("")) | 64 | .unwrap_or_else(|| RelativePath::new("")) |
65 | .join(&d.candidate); | 65 | .join(&d.candidate); |
66 | let create_file = FileSystemEdit::CreateFile { source_root, path }; | 66 | let fix = |
67 | let fix = SourceChange::file_system_edit("Create module", create_file); | 67 | Fix::new("Create module", FileSystemEdit::CreateFile { source_root, path }.into()); |
68 | res.borrow_mut().push(Diagnostic { | 68 | res.borrow_mut().push(Diagnostic { |
69 | range: sema.diagnostics_range(d).range, | 69 | range: sema.diagnostics_range(d).range, |
70 | message: d.message(), | 70 | message: d.message(), |
@@ -88,14 +88,12 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
88 | field_list = field_list.append_field(&field); | 88 | field_list = field_list.append_field(&field); |
89 | } | 89 | } |
90 | 90 | ||
91 | let mut builder = TextEditBuilder::default(); | 91 | let edit = { |
92 | algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); | 92 | let mut builder = TextEditBuilder::default(); |
93 | 93 | algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); | |
94 | Some(SourceChange::source_file_edit_from( | 94 | builder.finish() |
95 | "Fill struct fields", | 95 | }; |
96 | file_id, | 96 | Some(Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into())) |
97 | builder.finish(), | ||
98 | )) | ||
99 | }; | 97 | }; |
100 | 98 | ||
101 | res.borrow_mut().push(Diagnostic { | 99 | res.borrow_mut().push(Diagnostic { |
@@ -117,7 +115,8 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
117 | let node = d.ast(db); | 115 | let node = d.ast(db); |
118 | let replacement = format!("Ok({})", node.syntax()); | 116 | let replacement = format!("Ok({})", node.syntax()); |
119 | let edit = TextEdit::replace(node.syntax().text_range(), replacement); | 117 | let edit = TextEdit::replace(node.syntax().text_range(), replacement); |
120 | let fix = SourceChange::source_file_edit_from("Wrap with ok", file_id, edit); | 118 | let source_change = SourceChange::source_file_edit_from(file_id, edit); |
119 | let fix = Fix::new("Wrap with ok", source_change); | ||
121 | res.borrow_mut().push(Diagnostic { | 120 | res.borrow_mut().push(Diagnostic { |
122 | range: sema.diagnostics_range(d).range, | 121 | range: sema.diagnostics_range(d).range, |
123 | message: d.message(), | 122 | message: d.message(), |
@@ -154,9 +153,9 @@ fn check_unnecessary_braces_in_use_statement( | |||
154 | range, | 153 | range, |
155 | message: "Unnecessary braces in use statement".to_string(), | 154 | message: "Unnecessary braces in use statement".to_string(), |
156 | severity: Severity::WeakWarning, | 155 | severity: Severity::WeakWarning, |
157 | fix: Some(SourceChange::source_file_edit( | 156 | fix: Some(Fix::new( |
158 | "Remove unnecessary braces", | 157 | "Remove unnecessary braces", |
159 | SourceFileEdit { file_id, edit }, | 158 | SourceFileEdit { file_id, edit }.into(), |
160 | )), | 159 | )), |
161 | }); | 160 | }); |
162 | } | 161 | } |
@@ -198,9 +197,9 @@ fn check_struct_shorthand_initialization( | |||
198 | range: record_field.syntax().text_range(), | 197 | range: record_field.syntax().text_range(), |
199 | message: "Shorthand struct initialization".to_string(), | 198 | message: "Shorthand struct initialization".to_string(), |
200 | severity: Severity::WeakWarning, | 199 | severity: Severity::WeakWarning, |
201 | fix: Some(SourceChange::source_file_edit( | 200 | fix: Some(Fix::new( |
202 | "Use struct shorthand initialization", | 201 | "Use struct shorthand initialization", |
203 | SourceFileEdit { file_id, edit }, | 202 | SourceFileEdit { file_id, edit }.into(), |
204 | )), | 203 | )), |
205 | }); | 204 | }); |
206 | } | 205 | } |
@@ -240,7 +239,7 @@ mod tests { | |||
240 | let diagnostic = | 239 | let diagnostic = |
241 | diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); | 240 | diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); |
242 | let mut fix = diagnostic.fix.unwrap(); | 241 | let mut fix = diagnostic.fix.unwrap(); |
243 | let edit = fix.source_file_edits.pop().unwrap().edit; | 242 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; |
244 | let actual = { | 243 | let actual = { |
245 | let mut actual = before.to_string(); | 244 | let mut actual = before.to_string(); |
246 | edit.apply(&mut actual); | 245 | edit.apply(&mut actual); |
@@ -258,7 +257,7 @@ mod tests { | |||
258 | let (analysis, file_position) = analysis_and_position(fixture); | 257 | let (analysis, file_position) = analysis_and_position(fixture); |
259 | let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap(); | 258 | let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap(); |
260 | let mut fix = diagnostic.fix.unwrap(); | 259 | let mut fix = diagnostic.fix.unwrap(); |
261 | let edit = fix.source_file_edits.pop().unwrap().edit; | 260 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; |
262 | let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); | 261 | let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); |
263 | let actual = { | 262 | let actual = { |
264 | let mut actual = target_file_contents.to_string(); | 263 | let mut actual = target_file_contents.to_string(); |
@@ -295,7 +294,7 @@ mod tests { | |||
295 | let (analysis, file_id) = single_file(before); | 294 | let (analysis, file_id) = single_file(before); |
296 | let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); | 295 | let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); |
297 | let mut fix = diagnostic.fix.unwrap(); | 296 | let mut fix = diagnostic.fix.unwrap(); |
298 | let edit = fix.source_file_edits.pop().unwrap().edit; | 297 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; |
299 | let actual = { | 298 | let actual = { |
300 | let mut actual = before.to_string(); | 299 | let mut actual = before.to_string(); |
301 | edit.apply(&mut actual); | 300 | edit.apply(&mut actual); |
@@ -616,23 +615,24 @@ mod tests { | |||
616 | Diagnostic { | 615 | Diagnostic { |
617 | message: "unresolved module", | 616 | message: "unresolved module", |
618 | range: 0..8, | 617 | range: 0..8, |
618 | severity: Error, | ||
619 | fix: Some( | 619 | fix: Some( |
620 | SourceChange { | 620 | Fix { |
621 | label: "Create module", | 621 | label: "Create module", |
622 | source_file_edits: [], | 622 | source_change: SourceChange { |
623 | file_system_edits: [ | 623 | source_file_edits: [], |
624 | CreateFile { | 624 | file_system_edits: [ |
625 | source_root: SourceRootId( | 625 | CreateFile { |
626 | 0, | 626 | source_root: SourceRootId( |
627 | ), | 627 | 0, |
628 | path: "foo.rs", | 628 | ), |
629 | }, | 629 | path: "foo.rs", |
630 | ], | 630 | }, |
631 | cursor_position: None, | 631 | ], |
632 | is_snippet: false, | 632 | is_snippet: false, |
633 | }, | ||
633 | }, | 634 | }, |
634 | ), | 635 | ), |
635 | severity: Error, | ||
636 | }, | 636 | }, |
637 | ] | 637 | ] |
638 | "###); | 638 | "###); |
@@ -666,30 +666,31 @@ mod tests { | |||
666 | Diagnostic { | 666 | Diagnostic { |
667 | message: "Missing structure fields:\n- b", | 667 | message: "Missing structure fields:\n- b", |
668 | range: 224..233, | 668 | range: 224..233, |
669 | severity: Error, | ||
669 | fix: Some( | 670 | fix: Some( |
670 | SourceChange { | 671 | Fix { |
671 | label: "Fill struct fields", | 672 | label: "Fill struct fields", |
672 | source_file_edits: [ | 673 | source_change: SourceChange { |
673 | SourceFileEdit { | 674 | source_file_edits: [ |
674 | file_id: FileId( | 675 | SourceFileEdit { |
675 | 1, | 676 | file_id: FileId( |
676 | ), | 677 | 1, |
677 | edit: TextEdit { | 678 | ), |
678 | indels: [ | 679 | edit: TextEdit { |
679 | Indel { | 680 | indels: [ |
680 | insert: "{a:42, b: ()}", | 681 | Indel { |
681 | delete: 3..9, | 682 | insert: "{a:42, b: ()}", |
682 | }, | 683 | delete: 3..9, |
683 | ], | 684 | }, |
685 | ], | ||
686 | }, | ||
684 | }, | 687 | }, |
685 | }, | 688 | ], |
686 | ], | 689 | file_system_edits: [], |
687 | file_system_edits: [], | 690 | is_snippet: false, |
688 | cursor_position: None, | 691 | }, |
689 | is_snippet: false, | ||
690 | }, | 692 | }, |
691 | ), | 693 | ), |
692 | severity: Error, | ||
693 | }, | 694 | }, |
694 | ] | 695 | ] |
695 | "###); | 696 | "###); |
diff --git a/crates/ra_ide/src/display.rs b/crates/ra_ide/src/display.rs index 722092de9..8bb312156 100644 --- a/crates/ra_ide/src/display.rs +++ b/crates/ra_ide/src/display.rs | |||
@@ -79,14 +79,14 @@ pub(crate) fn rust_code_markup_with_doc( | |||
79 | doc: Option<&str>, | 79 | doc: Option<&str>, |
80 | mod_path: Option<&str>, | 80 | mod_path: Option<&str>, |
81 | ) -> String { | 81 | ) -> String { |
82 | let mut buf = "```rust\n".to_owned(); | 82 | let mut buf = String::new(); |
83 | 83 | ||
84 | if let Some(mod_path) = mod_path { | 84 | if let Some(mod_path) = mod_path { |
85 | if !mod_path.is_empty() { | 85 | if !mod_path.is_empty() { |
86 | format_to!(buf, "{}\n", mod_path); | 86 | format_to!(buf, "{}\n___\n\n", mod_path); |
87 | } | 87 | } |
88 | } | 88 | } |
89 | format_to!(buf, "{}\n```", code); | 89 | format_to!(buf, "```rust\n{}\n```", code); |
90 | 90 | ||
91 | if let Some(doc) = doc { | 91 | if let Some(doc) = doc { |
92 | format_to!(buf, "\n\n{}", doc); | 92 | format_to!(buf, "\n\n{}", doc); |
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index befa977c7..1f4f6b848 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -405,7 +405,7 @@ mod tests { | |||
405 | }; | 405 | }; |
406 | } | 406 | } |
407 | "#, | 407 | "#, |
408 | &["Foo\nfield_a: u32"], | 408 | &["Foo\n___\n\n```rust\nfield_a: u32"], |
409 | ); | 409 | ); |
410 | 410 | ||
411 | // Hovering over the field in the definition | 411 | // Hovering over the field in the definition |
@@ -422,7 +422,7 @@ mod tests { | |||
422 | }; | 422 | }; |
423 | } | 423 | } |
424 | "#, | 424 | "#, |
425 | &["Foo\nfield_a: u32"], | 425 | &["Foo\n___\n\n```rust\nfield_a: u32"], |
426 | ); | 426 | ); |
427 | } | 427 | } |
428 | 428 | ||
@@ -475,7 +475,7 @@ fn main() { | |||
475 | ", | 475 | ", |
476 | ); | 476 | ); |
477 | let hover = analysis.hover(position).unwrap().unwrap(); | 477 | let hover = analysis.hover(position).unwrap().unwrap(); |
478 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\nSome")); | 478 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\n___\n\n```rust\nSome")); |
479 | 479 | ||
480 | let (analysis, position) = single_file_with_position( | 480 | let (analysis, position) = single_file_with_position( |
481 | " | 481 | " |
@@ -503,6 +503,9 @@ fn main() { | |||
503 | "#, | 503 | "#, |
504 | &[" | 504 | &[" |
505 | Option | 505 | Option |
506 | ___ | ||
507 | |||
508 | ```rust | ||
506 | None | 509 | None |
507 | ``` | 510 | ``` |
508 | 511 | ||
@@ -524,6 +527,9 @@ The None variant | |||
524 | "#, | 527 | "#, |
525 | &[" | 528 | &[" |
526 | Option | 529 | Option |
530 | ___ | ||
531 | |||
532 | ```rust | ||
527 | Some | 533 | Some |
528 | ``` | 534 | ``` |
529 | 535 | ||
@@ -606,7 +612,10 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
606 | ", | 612 | ", |
607 | ); | 613 | ); |
608 | let hover = analysis.hover(position).unwrap().unwrap(); | 614 | let hover = analysis.hover(position).unwrap().unwrap(); |
609 | assert_eq!(trim_markup_opt(hover.info.first()), Some("wrapper::Thing\nfn new() -> Thing")); | 615 | assert_eq!( |
616 | trim_markup_opt(hover.info.first()), | ||
617 | Some("wrapper::Thing\n___\n\n```rust\nfn new() -> Thing") | ||
618 | ); | ||
610 | } | 619 | } |
611 | 620 | ||
612 | #[test] | 621 | #[test] |
diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs index d3af780c4..af1ade8a1 100644 --- a/crates/ra_ide/src/join_lines.rs +++ b/crates/ra_ide/src/join_lines.rs | |||
@@ -166,16 +166,28 @@ fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool { | |||
166 | 166 | ||
167 | #[cfg(test)] | 167 | #[cfg(test)] |
168 | mod tests { | 168 | mod tests { |
169 | use crate::test_utils::{assert_eq_text, check_action, extract_range}; | 169 | use ra_syntax::SourceFile; |
170 | use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range}; | ||
170 | 171 | ||
171 | use super::*; | 172 | use super::*; |
172 | 173 | ||
173 | fn check_join_lines(before: &str, after: &str) { | 174 | fn check_join_lines(before: &str, after: &str) { |
174 | check_action(before, after, |file, offset| { | 175 | let (before_cursor_pos, before) = extract_offset(before); |
175 | let range = TextRange::empty(offset); | 176 | let file = SourceFile::parse(&before).ok().unwrap(); |
176 | let res = join_lines(file, range); | 177 | |
177 | Some(res) | 178 | let range = TextRange::empty(before_cursor_pos); |
178 | }) | 179 | let result = join_lines(&file, range); |
180 | |||
181 | let actual = { | ||
182 | let mut actual = before.to_string(); | ||
183 | result.apply(&mut actual); | ||
184 | actual | ||
185 | }; | ||
186 | let actual_cursor_pos = result | ||
187 | .apply_to_offset(before_cursor_pos) | ||
188 | .expect("cursor position is affected by the edit"); | ||
189 | let actual = add_cursor(&actual, actual_cursor_pos); | ||
190 | assert_eq_text!(after, &actual); | ||
179 | } | 191 | } |
180 | 192 | ||
181 | #[test] | 193 | #[test] |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 83cb498f7..5ac002d82 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -42,9 +42,6 @@ mod inlay_hints; | |||
42 | mod expand_macro; | 42 | mod expand_macro; |
43 | mod ssr; | 43 | mod ssr; |
44 | 44 | ||
45 | #[cfg(test)] | ||
46 | mod test_utils; | ||
47 | |||
48 | use std::sync::Arc; | 45 | use std::sync::Arc; |
49 | 46 | ||
50 | use ra_cfg::CfgOptions; | 47 | use ra_cfg::CfgOptions; |
@@ -87,12 +84,12 @@ pub use ra_db::{ | |||
87 | pub use ra_ide_db::{ | 84 | pub use ra_ide_db::{ |
88 | change::{AnalysisChange, LibraryData}, | 85 | change::{AnalysisChange, LibraryData}, |
89 | line_index::{LineCol, LineIndex}, | 86 | line_index::{LineCol, LineIndex}, |
90 | line_index_utils::translate_offset_with_edit, | ||
91 | search::SearchScope, | 87 | search::SearchScope, |
92 | source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, | 88 | source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, |
93 | symbol_index::Query, | 89 | symbol_index::Query, |
94 | RootDatabase, | 90 | RootDatabase, |
95 | }; | 91 | }; |
92 | pub use ra_text_edit::{Indel, TextEdit}; | ||
96 | 93 | ||
97 | pub type Cancelable<T> = Result<T, Canceled>; | 94 | pub type Cancelable<T> = Result<T, Canceled>; |
98 | 95 | ||
@@ -100,8 +97,22 @@ pub type Cancelable<T> = Result<T, Canceled>; | |||
100 | pub struct Diagnostic { | 97 | pub struct Diagnostic { |
101 | pub message: String, | 98 | pub message: String, |
102 | pub range: TextRange, | 99 | pub range: TextRange, |
103 | pub fix: Option<SourceChange>, | ||
104 | pub severity: Severity, | 100 | pub severity: Severity, |
101 | pub fix: Option<Fix>, | ||
102 | } | ||
103 | |||
104 | #[derive(Debug)] | ||
105 | pub struct Fix { | ||
106 | pub label: String, | ||
107 | pub source_change: SourceChange, | ||
108 | } | ||
109 | |||
110 | impl Fix { | ||
111 | pub fn new(label: impl Into<String>, source_change: SourceChange) -> Self { | ||
112 | let label = label.into(); | ||
113 | assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.')); | ||
114 | Self { label, source_change } | ||
115 | } | ||
105 | } | 116 | } |
106 | 117 | ||
107 | /// Info associated with a text range. | 118 | /// Info associated with a text range. |
@@ -289,14 +300,10 @@ impl Analysis { | |||
289 | 300 | ||
290 | /// Returns an edit to remove all newlines in the range, cleaning up minor | 301 | /// Returns an edit to remove all newlines in the range, cleaning up minor |
291 | /// stuff like trailing commas. | 302 | /// stuff like trailing commas. |
292 | pub fn join_lines(&self, frange: FileRange) -> Cancelable<SourceChange> { | 303 | pub fn join_lines(&self, frange: FileRange) -> Cancelable<TextEdit> { |
293 | self.with_db(|db| { | 304 | self.with_db(|db| { |
294 | let parse = db.parse(frange.file_id); | 305 | let parse = db.parse(frange.file_id); |
295 | let file_edit = SourceFileEdit { | 306 | join_lines::join_lines(&parse.tree(), frange.range) |
296 | file_id: frange.file_id, | ||
297 | edit: join_lines::join_lines(&parse.tree(), frange.range), | ||
298 | }; | ||
299 | SourceChange::source_file_edit("Join lines", file_edit) | ||
300 | }) | 307 | }) |
301 | } | 308 | } |
302 | 309 | ||
@@ -500,7 +507,7 @@ impl Analysis { | |||
500 | ) -> Cancelable<Result<SourceChange, SsrError>> { | 507 | ) -> Cancelable<Result<SourceChange, SsrError>> { |
501 | self.with_db(|db| { | 508 | self.with_db(|db| { |
502 | let edits = ssr::parse_search_replace(query, parse_only, db)?; | 509 | let edits = ssr::parse_search_replace(query, parse_only, db)?; |
503 | Ok(SourceChange::source_file_edits("Structural Search Replace", edits)) | 510 | Ok(SourceChange::source_file_edits(edits)) |
504 | }) | 511 | }) |
505 | } | 512 | } |
506 | 513 | ||
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index 62ec8d85d..28c6349b1 100644 --- a/crates/ra_ide/src/references/rename.rs +++ b/crates/ra_ide/src/references/rename.rs | |||
@@ -128,7 +128,7 @@ fn rename_mod( | |||
128 | source_file_edits.extend(ref_edits); | 128 | source_file_edits.extend(ref_edits); |
129 | } | 129 | } |
130 | 130 | ||
131 | Some(SourceChange::from_edits("Rename", source_file_edits, file_system_edits)) | 131 | Some(SourceChange::from_edits(source_file_edits, file_system_edits)) |
132 | } | 132 | } |
133 | 133 | ||
134 | fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> { | 134 | fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> { |
@@ -171,7 +171,7 @@ fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo | |||
171 | ), | 171 | ), |
172 | }); | 172 | }); |
173 | 173 | ||
174 | Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits))) | 174 | Some(RangeInfo::new(range, SourceChange::source_file_edits(edits))) |
175 | } | 175 | } |
176 | 176 | ||
177 | fn text_edit_from_self_param( | 177 | fn text_edit_from_self_param( |
@@ -234,7 +234,7 @@ fn rename_self_to_param( | |||
234 | let range = ast::SelfParam::cast(self_token.parent()) | 234 | let range = ast::SelfParam::cast(self_token.parent()) |
235 | .map_or(self_token.text_range(), |p| p.syntax().text_range()); | 235 | .map_or(self_token.text_range(), |p| p.syntax().text_range()); |
236 | 236 | ||
237 | Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits))) | 237 | Some(RangeInfo::new(range, SourceChange::source_file_edits(edits))) |
238 | } | 238 | } |
239 | 239 | ||
240 | fn rename_reference( | 240 | fn rename_reference( |
@@ -253,7 +253,7 @@ fn rename_reference( | |||
253 | return None; | 253 | return None; |
254 | } | 254 | } |
255 | 255 | ||
256 | Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edit))) | 256 | Some(RangeInfo::new(range, SourceChange::source_file_edits(edit))) |
257 | } | 257 | } |
258 | 258 | ||
259 | #[cfg(test)] | 259 | #[cfg(test)] |
@@ -642,7 +642,6 @@ mod tests { | |||
642 | RangeInfo { | 642 | RangeInfo { |
643 | range: 4..7, | 643 | range: 4..7, |
644 | info: SourceChange { | 644 | info: SourceChange { |
645 | label: "Rename", | ||
646 | source_file_edits: [ | 645 | source_file_edits: [ |
647 | SourceFileEdit { | 646 | SourceFileEdit { |
648 | file_id: FileId( | 647 | file_id: FileId( |
@@ -669,7 +668,6 @@ mod tests { | |||
669 | dst_path: "bar/foo2.rs", | 668 | dst_path: "bar/foo2.rs", |
670 | }, | 669 | }, |
671 | ], | 670 | ], |
672 | cursor_position: None, | ||
673 | is_snippet: false, | 671 | is_snippet: false, |
674 | }, | 672 | }, |
675 | }, | 673 | }, |
@@ -695,7 +693,6 @@ mod tests { | |||
695 | RangeInfo { | 693 | RangeInfo { |
696 | range: 4..7, | 694 | range: 4..7, |
697 | info: SourceChange { | 695 | info: SourceChange { |
698 | label: "Rename", | ||
699 | source_file_edits: [ | 696 | source_file_edits: [ |
700 | SourceFileEdit { | 697 | SourceFileEdit { |
701 | file_id: FileId( | 698 | file_id: FileId( |
@@ -722,7 +719,6 @@ mod tests { | |||
722 | dst_path: "foo2/mod.rs", | 719 | dst_path: "foo2/mod.rs", |
723 | }, | 720 | }, |
724 | ], | 721 | ], |
725 | cursor_position: None, | ||
726 | is_snippet: false, | 722 | is_snippet: false, |
727 | }, | 723 | }, |
728 | }, | 724 | }, |
@@ -779,7 +775,6 @@ mod tests { | |||
779 | RangeInfo { | 775 | RangeInfo { |
780 | range: 8..11, | 776 | range: 8..11, |
781 | info: SourceChange { | 777 | info: SourceChange { |
782 | label: "Rename", | ||
783 | source_file_edits: [ | 778 | source_file_edits: [ |
784 | SourceFileEdit { | 779 | SourceFileEdit { |
785 | file_id: FileId( | 780 | file_id: FileId( |
@@ -819,7 +814,6 @@ mod tests { | |||
819 | dst_path: "bar/foo2.rs", | 814 | dst_path: "bar/foo2.rs", |
820 | }, | 815 | }, |
821 | ], | 816 | ], |
822 | cursor_position: None, | ||
823 | is_snippet: false, | 817 | is_snippet: false, |
824 | }, | 818 | }, |
825 | }, | 819 | }, |
@@ -986,8 +980,8 @@ mod tests { | |||
986 | if let Some(change) = source_change { | 980 | if let Some(change) = source_change { |
987 | for edit in change.info.source_file_edits { | 981 | for edit in change.info.source_file_edits { |
988 | file_id = Some(edit.file_id); | 982 | file_id = Some(edit.file_id); |
989 | for indel in edit.edit.as_indels() { | 983 | for indel in edit.edit.into_iter() { |
990 | text_edit_builder.replace(indel.delete, indel.insert.clone()); | 984 | text_edit_builder.replace(indel.delete, indel.insert); |
991 | } | 985 | } |
992 | } | 986 | } |
993 | } | 987 | } |
diff --git a/crates/ra_ide/src/test_utils.rs b/crates/ra_ide/src/test_utils.rs deleted file mode 100644 index 48c8fd1f4..000000000 --- a/crates/ra_ide/src/test_utils.rs +++ /dev/null | |||
@@ -1,25 +0,0 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use ra_syntax::{SourceFile, TextSize}; | ||
4 | use ra_text_edit::TextEdit; | ||
5 | |||
6 | pub use test_utils::*; | ||
7 | |||
8 | pub fn check_action<F: Fn(&SourceFile, TextSize) -> Option<TextEdit>>( | ||
9 | before: &str, | ||
10 | after: &str, | ||
11 | f: F, | ||
12 | ) { | ||
13 | let (before_cursor_pos, before) = extract_offset(before); | ||
14 | let file = SourceFile::parse(&before).ok().unwrap(); | ||
15 | let result = f(&file, before_cursor_pos).expect("code action is not applicable"); | ||
16 | let actual = { | ||
17 | let mut actual = before.to_string(); | ||
18 | result.apply(&mut actual); | ||
19 | actual | ||
20 | }; | ||
21 | let actual_cursor_pos = | ||
22 | result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit"); | ||
23 | let actual = add_cursor(&actual, actual_cursor_pos); | ||
24 | assert_eq_text!(after, &actual); | ||
25 | } | ||
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs index cd48cad93..39bb3b357 100644 --- a/crates/ra_ide/src/typing.rs +++ b/crates/ra_ide/src/typing.rs | |||
@@ -17,7 +17,7 @@ mod on_enter; | |||
17 | 17 | ||
18 | use ra_db::{FilePosition, SourceDatabase}; | 18 | use ra_db::{FilePosition, SourceDatabase}; |
19 | use ra_fmt::leading_indent; | 19 | use ra_fmt::leading_indent; |
20 | use ra_ide_db::{source_change::SingleFileChange, RootDatabase}; | 20 | use ra_ide_db::RootDatabase; |
21 | use ra_syntax::{ | 21 | use ra_syntax::{ |
22 | algo::find_node_at_offset, | 22 | algo::find_node_at_offset, |
23 | ast::{self, AstToken}, | 23 | ast::{self, AstToken}, |
@@ -40,15 +40,11 @@ pub(crate) fn on_char_typed( | |||
40 | assert!(TRIGGER_CHARS.contains(char_typed)); | 40 | assert!(TRIGGER_CHARS.contains(char_typed)); |
41 | let file = &db.parse(position.file_id).tree(); | 41 | let file = &db.parse(position.file_id).tree(); |
42 | assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); | 42 | assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); |
43 | let single_file_change = on_char_typed_inner(file, position.offset, char_typed)?; | 43 | let text_edit = on_char_typed_inner(file, position.offset, char_typed)?; |
44 | Some(single_file_change.into_source_change(position.file_id)) | 44 | Some(SourceChange::source_file_edit_from(position.file_id, text_edit)) |
45 | } | 45 | } |
46 | 46 | ||
47 | fn on_char_typed_inner( | 47 | fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> { |
48 | file: &SourceFile, | ||
49 | offset: TextSize, | ||
50 | char_typed: char, | ||
51 | ) -> Option<SingleFileChange> { | ||
52 | assert!(TRIGGER_CHARS.contains(char_typed)); | 48 | assert!(TRIGGER_CHARS.contains(char_typed)); |
53 | match char_typed { | 49 | match char_typed { |
54 | '.' => on_dot_typed(file, offset), | 50 | '.' => on_dot_typed(file, offset), |
@@ -61,7 +57,7 @@ fn on_char_typed_inner( | |||
61 | /// Returns an edit which should be applied after `=` was typed. Primarily, | 57 | /// Returns an edit which should be applied after `=` was typed. Primarily, |
62 | /// this works when adding `let =`. | 58 | /// this works when adding `let =`. |
63 | // FIXME: use a snippet completion instead of this hack here. | 59 | // FIXME: use a snippet completion instead of this hack here. |
64 | fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> { | 60 | fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { |
65 | assert_eq!(file.syntax().text().char_at(offset), Some('=')); | 61 | assert_eq!(file.syntax().text().char_at(offset), Some('=')); |
66 | let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; | 62 | let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; |
67 | if let_stmt.semicolon_token().is_some() { | 63 | if let_stmt.semicolon_token().is_some() { |
@@ -79,14 +75,11 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> | |||
79 | return None; | 75 | return None; |
80 | } | 76 | } |
81 | let offset = let_stmt.syntax().text_range().end(); | 77 | let offset = let_stmt.syntax().text_range().end(); |
82 | Some(SingleFileChange { | 78 | Some(TextEdit::insert(offset, ";".to_string())) |
83 | label: "add semicolon".to_string(), | ||
84 | edit: TextEdit::insert(offset, ";".to_string()), | ||
85 | }) | ||
86 | } | 79 | } |
87 | 80 | ||
88 | /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. | 81 | /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. |
89 | fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> { | 82 | fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { |
90 | assert_eq!(file.syntax().text().char_at(offset), Some('.')); | 83 | assert_eq!(file.syntax().text().char_at(offset), Some('.')); |
91 | let whitespace = | 84 | let whitespace = |
92 | file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; | 85 | file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; |
@@ -107,14 +100,11 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> | |||
107 | return None; | 100 | return None; |
108 | } | 101 | } |
109 | 102 | ||
110 | Some(SingleFileChange { | 103 | Some(TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent)) |
111 | label: "reindent dot".to_string(), | ||
112 | edit: TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent), | ||
113 | }) | ||
114 | } | 104 | } |
115 | 105 | ||
116 | /// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }` | 106 | /// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }` |
117 | fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> { | 107 | fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { |
118 | let file_text = file.syntax().text(); | 108 | let file_text = file.syntax().text(); |
119 | assert_eq!(file_text.char_at(offset), Some('>')); | 109 | assert_eq!(file_text.char_at(offset), Some('>')); |
120 | let after_arrow = offset + TextSize::of('>'); | 110 | let after_arrow = offset + TextSize::of('>'); |
@@ -125,10 +115,7 @@ fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChang | |||
125 | return None; | 115 | return None; |
126 | } | 116 | } |
127 | 117 | ||
128 | Some(SingleFileChange { | 118 | Some(TextEdit::insert(after_arrow, " ".to_string())) |
129 | label: "add space after return type".to_string(), | ||
130 | edit: TextEdit::insert(after_arrow, " ".to_string()), | ||
131 | }) | ||
132 | } | 119 | } |
133 | 120 | ||
134 | #[cfg(test)] | 121 | #[cfg(test)] |
@@ -144,7 +131,7 @@ mod tests { | |||
144 | edit.apply(&mut before); | 131 | edit.apply(&mut before); |
145 | let parse = SourceFile::parse(&before); | 132 | let parse = SourceFile::parse(&before); |
146 | on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| { | 133 | on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| { |
147 | it.edit.apply(&mut before); | 134 | it.apply(&mut before); |
148 | before.to_string() | 135 | before.to_string() |
149 | }) | 136 | }) |
150 | } | 137 | } |
diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs index 78a40cc94..e7d64b4f6 100644 --- a/crates/ra_ide/src/typing/on_enter.rs +++ b/crates/ra_ide/src/typing/on_enter.rs | |||
@@ -38,17 +38,12 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Sour | |||
38 | } | 38 | } |
39 | 39 | ||
40 | let indent = node_indent(&file, comment.syntax())?; | 40 | let indent = node_indent(&file, comment.syntax())?; |
41 | let inserted = format!("\n{}{} ", indent, prefix); | 41 | let inserted = format!("\n{}{} $0", indent, prefix); |
42 | let cursor_position = position.offset + TextSize::of(&inserted); | ||
43 | let edit = TextEdit::insert(position.offset, inserted); | 42 | let edit = TextEdit::insert(position.offset, inserted); |
44 | 43 | ||
45 | Some( | 44 | let mut res = SourceChange::from(SourceFileEdit { edit, file_id: position.file_id }); |
46 | SourceChange::source_file_edit( | 45 | res.is_snippet = true; |
47 | "On enter", | 46 | Some(res) |
48 | SourceFileEdit { edit, file_id: position.file_id }, | ||
49 | ) | ||
50 | .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }), | ||
51 | ) | ||
52 | } | 47 | } |
53 | 48 | ||
54 | fn followed_by_comment(comment: &ast::Comment) -> bool { | 49 | fn followed_by_comment(comment: &ast::Comment) -> bool { |
@@ -84,7 +79,7 @@ fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option<SmolStr> { | |||
84 | 79 | ||
85 | #[cfg(test)] | 80 | #[cfg(test)] |
86 | mod tests { | 81 | mod tests { |
87 | use test_utils::{add_cursor, assert_eq_text, extract_offset}; | 82 | use test_utils::{assert_eq_text, extract_offset}; |
88 | 83 | ||
89 | use crate::mock_analysis::single_file; | 84 | use crate::mock_analysis::single_file; |
90 | 85 | ||
@@ -98,7 +93,6 @@ mod tests { | |||
98 | assert_eq!(result.source_file_edits.len(), 1); | 93 | assert_eq!(result.source_file_edits.len(), 1); |
99 | let mut actual = before.to_string(); | 94 | let mut actual = before.to_string(); |
100 | result.source_file_edits[0].edit.apply(&mut actual); | 95 | result.source_file_edits[0].edit.apply(&mut actual); |
101 | let actual = add_cursor(&actual, result.cursor_position.unwrap().offset); | ||
102 | Some(actual) | 96 | Some(actual) |
103 | } | 97 | } |
104 | 98 | ||
@@ -121,7 +115,7 @@ fn foo() { | |||
121 | ", | 115 | ", |
122 | r" | 116 | r" |
123 | /// Some docs | 117 | /// Some docs |
124 | /// <|> | 118 | /// $0 |
125 | fn foo() { | 119 | fn foo() { |
126 | } | 120 | } |
127 | ", | 121 | ", |
@@ -137,7 +131,7 @@ impl S { | |||
137 | r" | 131 | r" |
138 | impl S { | 132 | impl S { |
139 | /// Some | 133 | /// Some |
140 | /// <|> docs. | 134 | /// $0 docs. |
141 | fn foo() {} | 135 | fn foo() {} |
142 | } | 136 | } |
143 | ", | 137 | ", |
@@ -151,7 +145,7 @@ fn foo() { | |||
151 | ", | 145 | ", |
152 | r" | 146 | r" |
153 | /// | 147 | /// |
154 | /// <|> Some docs | 148 | /// $0 Some docs |
155 | fn foo() { | 149 | fn foo() { |
156 | } | 150 | } |
157 | ", | 151 | ", |
@@ -175,7 +169,7 @@ fn main() { | |||
175 | r" | 169 | r" |
176 | fn main() { | 170 | fn main() { |
177 | // Fix | 171 | // Fix |
178 | // <|> me | 172 | // $0 me |
179 | let x = 1 + 1; | 173 | let x = 1 + 1; |
180 | } | 174 | } |
181 | ", | 175 | ", |
@@ -195,7 +189,7 @@ fn main() { | |||
195 | r" | 189 | r" |
196 | fn main() { | 190 | fn main() { |
197 | // Fix | 191 | // Fix |
198 | // <|> | 192 | // $0 |
199 | // me | 193 | // me |
200 | let x = 1 + 1; | 194 | let x = 1 + 1; |
201 | } | 195 | } |
diff --git a/crates/ra_ide_db/src/lib.rs b/crates/ra_ide_db/src/lib.rs index 4f37954bf..1b74e6558 100644 --- a/crates/ra_ide_db/src/lib.rs +++ b/crates/ra_ide_db/src/lib.rs | |||
@@ -3,7 +3,6 @@ | |||
3 | //! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search. | 3 | //! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search. |
4 | 4 | ||
5 | pub mod line_index; | 5 | pub mod line_index; |
6 | pub mod line_index_utils; | ||
7 | pub mod symbol_index; | 6 | pub mod symbol_index; |
8 | pub mod change; | 7 | pub mod change; |
9 | pub mod defs; | 8 | pub mod defs; |
diff --git a/crates/ra_ide_db/src/line_index_utils.rs b/crates/ra_ide_db/src/line_index_utils.rs deleted file mode 100644 index 7fa6fc448..000000000 --- a/crates/ra_ide_db/src/line_index_utils.rs +++ /dev/null | |||
@@ -1,302 +0,0 @@ | |||
1 | //! Code actions can specify desirable final position of the cursor. | ||
2 | //! | ||
3 | //! The position is specified as a `TextSize` in the final file. We need to send | ||
4 | //! it in `(Line, Column)` coordinate though. However, we only have a LineIndex | ||
5 | //! for a file pre-edit! | ||
6 | //! | ||
7 | //! Code in this module applies this "to (Line, Column) after edit" | ||
8 | //! transformation. | ||
9 | |||
10 | use std::convert::TryInto; | ||
11 | |||
12 | use ra_syntax::{TextRange, TextSize}; | ||
13 | use ra_text_edit::{Indel, TextEdit}; | ||
14 | |||
15 | use crate::line_index::{LineCol, LineIndex, Utf16Char}; | ||
16 | |||
17 | pub fn translate_offset_with_edit( | ||
18 | line_index: &LineIndex, | ||
19 | offset: TextSize, | ||
20 | text_edit: &TextEdit, | ||
21 | ) -> LineCol { | ||
22 | let mut state = Edits::from_text_edit(&text_edit); | ||
23 | |||
24 | let mut res = RunningLineCol::new(); | ||
25 | |||
26 | macro_rules! test_step { | ||
27 | ($x:ident) => { | ||
28 | match &$x { | ||
29 | Step::Newline(n) => { | ||
30 | if offset < *n { | ||
31 | return res.to_line_col(offset); | ||
32 | } else { | ||
33 | res.add_line(*n); | ||
34 | } | ||
35 | } | ||
36 | Step::Utf16Char(x) => { | ||
37 | if offset < x.end() { | ||
38 | // if the offset is inside a multibyte char it's invalid | ||
39 | // clamp it to the start of the char | ||
40 | let clamp = offset.min(x.start()); | ||
41 | return res.to_line_col(clamp); | ||
42 | } else { | ||
43 | res.adjust_col(*x); | ||
44 | } | ||
45 | } | ||
46 | } | ||
47 | }; | ||
48 | } | ||
49 | |||
50 | for orig_step in LineIndexStepIter::from(line_index) { | ||
51 | loop { | ||
52 | let translated_step = state.translate_step(&orig_step); | ||
53 | match state.next_steps(&translated_step) { | ||
54 | NextSteps::Use => { | ||
55 | test_step!(translated_step); | ||
56 | break; | ||
57 | } | ||
58 | NextSteps::ReplaceMany(ns) => { | ||
59 | for n in ns { | ||
60 | test_step!(n); | ||
61 | } | ||
62 | break; | ||
63 | } | ||
64 | NextSteps::AddMany(ns) => { | ||
65 | for n in ns { | ||
66 | test_step!(n); | ||
67 | } | ||
68 | } | ||
69 | } | ||
70 | } | ||
71 | } | ||
72 | |||
73 | loop { | ||
74 | match state.next_inserted_steps() { | ||
75 | None => break, | ||
76 | Some(ns) => { | ||
77 | for n in ns { | ||
78 | test_step!(n); | ||
79 | } | ||
80 | } | ||
81 | } | ||
82 | } | ||
83 | |||
84 | res.to_line_col(offset) | ||
85 | } | ||
86 | |||
87 | #[derive(Debug, Clone)] | ||
88 | enum Step { | ||
89 | Newline(TextSize), | ||
90 | Utf16Char(TextRange), | ||
91 | } | ||
92 | |||
93 | #[derive(Debug)] | ||
94 | struct LineIndexStepIter<'a> { | ||
95 | line_index: &'a LineIndex, | ||
96 | next_newline_idx: usize, | ||
97 | utf16_chars: Option<(TextSize, std::slice::Iter<'a, Utf16Char>)>, | ||
98 | } | ||
99 | |||
100 | impl LineIndexStepIter<'_> { | ||
101 | fn from(line_index: &LineIndex) -> LineIndexStepIter { | ||
102 | let mut x = LineIndexStepIter { line_index, next_newline_idx: 0, utf16_chars: None }; | ||
103 | // skip first newline since it's not real | ||
104 | x.next(); | ||
105 | x | ||
106 | } | ||
107 | } | ||
108 | |||
109 | impl Iterator for LineIndexStepIter<'_> { | ||
110 | type Item = Step; | ||
111 | fn next(&mut self) -> Option<Step> { | ||
112 | self.utf16_chars | ||
113 | .as_mut() | ||
114 | .and_then(|(newline, x)| { | ||
115 | let x = x.next()?; | ||
116 | Some(Step::Utf16Char(TextRange::new(*newline + x.start, *newline + x.end))) | ||
117 | }) | ||
118 | .or_else(|| { | ||
119 | let next_newline = *self.line_index.newlines.get(self.next_newline_idx)?; | ||
120 | self.utf16_chars = self | ||
121 | .line_index | ||
122 | .utf16_lines | ||
123 | .get(&(self.next_newline_idx as u32)) | ||
124 | .map(|x| (next_newline, x.iter())); | ||
125 | self.next_newline_idx += 1; | ||
126 | Some(Step::Newline(next_newline)) | ||
127 | }) | ||
128 | } | ||
129 | } | ||
130 | |||
131 | #[derive(Debug)] | ||
132 | struct OffsetStepIter<'a> { | ||
133 | text: &'a str, | ||
134 | offset: TextSize, | ||
135 | } | ||
136 | |||
137 | impl Iterator for OffsetStepIter<'_> { | ||
138 | type Item = Step; | ||
139 | fn next(&mut self) -> Option<Step> { | ||
140 | let (next, next_offset) = self | ||
141 | .text | ||
142 | .char_indices() | ||
143 | .filter_map(|(i, c)| { | ||
144 | let i: TextSize = i.try_into().unwrap(); | ||
145 | let char_len = TextSize::of(c); | ||
146 | if c == '\n' { | ||
147 | let next_offset = self.offset + i + char_len; | ||
148 | let next = Step::Newline(next_offset); | ||
149 | Some((next, next_offset)) | ||
150 | } else { | ||
151 | if !c.is_ascii() { | ||
152 | let start = self.offset + i; | ||
153 | let end = start + char_len; | ||
154 | let next = Step::Utf16Char(TextRange::new(start, end)); | ||
155 | let next_offset = end; | ||
156 | Some((next, next_offset)) | ||
157 | } else { | ||
158 | None | ||
159 | } | ||
160 | } | ||
161 | }) | ||
162 | .next()?; | ||
163 | let next_idx: usize = (next_offset - self.offset).into(); | ||
164 | self.text = &self.text[next_idx..]; | ||
165 | self.offset = next_offset; | ||
166 | Some(next) | ||
167 | } | ||
168 | } | ||
169 | |||
170 | #[derive(Debug)] | ||
171 | enum NextSteps<'a> { | ||
172 | Use, | ||
173 | ReplaceMany(OffsetStepIter<'a>), | ||
174 | AddMany(OffsetStepIter<'a>), | ||
175 | } | ||
176 | |||
177 | #[derive(Debug)] | ||
178 | struct TranslatedEdit<'a> { | ||
179 | delete: TextRange, | ||
180 | insert: &'a str, | ||
181 | diff: i64, | ||
182 | } | ||
183 | |||
184 | struct Edits<'a> { | ||
185 | edits: &'a [Indel], | ||
186 | current: Option<TranslatedEdit<'a>>, | ||
187 | acc_diff: i64, | ||
188 | } | ||
189 | |||
190 | impl<'a> Edits<'a> { | ||
191 | fn from_text_edit(text_edit: &'a TextEdit) -> Edits<'a> { | ||
192 | let mut x = Edits { edits: text_edit.as_indels(), current: None, acc_diff: 0 }; | ||
193 | x.advance_edit(); | ||
194 | x | ||
195 | } | ||
196 | fn advance_edit(&mut self) { | ||
197 | self.acc_diff += self.current.as_ref().map_or(0, |x| x.diff); | ||
198 | match self.edits.split_first() { | ||
199 | Some((next, rest)) => { | ||
200 | let delete = self.translate_range(next.delete); | ||
201 | let diff = next.insert.len() as i64 - usize::from(next.delete.len()) as i64; | ||
202 | self.current = Some(TranslatedEdit { delete, insert: &next.insert, diff }); | ||
203 | self.edits = rest; | ||
204 | } | ||
205 | None => { | ||
206 | self.current = None; | ||
207 | } | ||
208 | } | ||
209 | } | ||
210 | |||
211 | fn next_inserted_steps(&mut self) -> Option<OffsetStepIter<'a>> { | ||
212 | let cur = self.current.as_ref()?; | ||
213 | let res = Some(OffsetStepIter { offset: cur.delete.start(), text: &cur.insert }); | ||
214 | self.advance_edit(); | ||
215 | res | ||
216 | } | ||
217 | |||
218 | fn next_steps(&mut self, step: &Step) -> NextSteps { | ||
219 | let step_pos = match *step { | ||
220 | Step::Newline(n) => n, | ||
221 | Step::Utf16Char(r) => r.end(), | ||
222 | }; | ||
223 | match &mut self.current { | ||
224 | Some(edit) => { | ||
225 | if step_pos <= edit.delete.start() { | ||
226 | NextSteps::Use | ||
227 | } else if step_pos <= edit.delete.end() { | ||
228 | let iter = OffsetStepIter { offset: edit.delete.start(), text: &edit.insert }; | ||
229 | // empty slice to avoid returning steps again | ||
230 | edit.insert = &edit.insert[edit.insert.len()..]; | ||
231 | NextSteps::ReplaceMany(iter) | ||
232 | } else { | ||
233 | let iter = OffsetStepIter { offset: edit.delete.start(), text: &edit.insert }; | ||
234 | // empty slice to avoid returning steps again | ||
235 | edit.insert = &edit.insert[edit.insert.len()..]; | ||
236 | self.advance_edit(); | ||
237 | NextSteps::AddMany(iter) | ||
238 | } | ||
239 | } | ||
240 | None => NextSteps::Use, | ||
241 | } | ||
242 | } | ||
243 | |||
244 | fn translate_range(&self, range: TextRange) -> TextRange { | ||
245 | if self.acc_diff == 0 { | ||
246 | range | ||
247 | } else { | ||
248 | let start = self.translate(range.start()); | ||
249 | let end = self.translate(range.end()); | ||
250 | TextRange::new(start, end) | ||
251 | } | ||
252 | } | ||
253 | |||
254 | fn translate(&self, x: TextSize) -> TextSize { | ||
255 | if self.acc_diff == 0 { | ||
256 | x | ||
257 | } else { | ||
258 | TextSize::from((usize::from(x) as i64 + self.acc_diff) as u32) | ||
259 | } | ||
260 | } | ||
261 | |||
262 | fn translate_step(&self, x: &Step) -> Step { | ||
263 | if self.acc_diff == 0 { | ||
264 | x.clone() | ||
265 | } else { | ||
266 | match *x { | ||
267 | Step::Newline(n) => Step::Newline(self.translate(n)), | ||
268 | Step::Utf16Char(r) => Step::Utf16Char(self.translate_range(r)), | ||
269 | } | ||
270 | } | ||
271 | } | ||
272 | } | ||
273 | |||
274 | #[derive(Debug)] | ||
275 | struct RunningLineCol { | ||
276 | line: u32, | ||
277 | last_newline: TextSize, | ||
278 | col_adjust: TextSize, | ||
279 | } | ||
280 | |||
281 | impl RunningLineCol { | ||
282 | fn new() -> RunningLineCol { | ||
283 | RunningLineCol { line: 0, last_newline: TextSize::from(0), col_adjust: TextSize::from(0) } | ||
284 | } | ||
285 | |||
286 | fn to_line_col(&self, offset: TextSize) -> LineCol { | ||
287 | LineCol { | ||
288 | line: self.line, | ||
289 | col_utf16: ((offset - self.last_newline) - self.col_adjust).into(), | ||
290 | } | ||
291 | } | ||
292 | |||
293 | fn add_line(&mut self, newline: TextSize) { | ||
294 | self.line += 1; | ||
295 | self.last_newline = newline; | ||
296 | self.col_adjust = TextSize::from(0); | ||
297 | } | ||
298 | |||
299 | fn adjust_col(&mut self, range: TextRange) { | ||
300 | self.col_adjust += range.len() - TextSize::from(1); | ||
301 | } | ||
302 | } | ||
diff --git a/crates/ra_ide_db/src/source_change.rs b/crates/ra_ide_db/src/source_change.rs index 94e118dd8..e713f4b7e 100644 --- a/crates/ra_ide_db/src/source_change.rs +++ b/crates/ra_ide_db/src/source_change.rs | |||
@@ -3,94 +3,35 @@ | |||
3 | //! | 3 | //! |
4 | //! It can be viewed as a dual for `AnalysisChange`. | 4 | //! It can be viewed as a dual for `AnalysisChange`. |
5 | 5 | ||
6 | use ra_db::{FileId, FilePosition, RelativePathBuf, SourceRootId}; | 6 | use ra_db::{FileId, RelativePathBuf, SourceRootId}; |
7 | use ra_text_edit::TextEdit; | 7 | use ra_text_edit::TextEdit; |
8 | 8 | ||
9 | #[derive(Debug, Clone)] | 9 | #[derive(Debug, Clone)] |
10 | pub struct SourceChange { | 10 | pub struct SourceChange { |
11 | /// For display in the undo log in the editor | ||
12 | pub label: String, | ||
13 | pub source_file_edits: Vec<SourceFileEdit>, | 11 | pub source_file_edits: Vec<SourceFileEdit>, |
14 | pub file_system_edits: Vec<FileSystemEdit>, | 12 | pub file_system_edits: Vec<FileSystemEdit>, |
15 | pub cursor_position: Option<FilePosition>, | ||
16 | pub is_snippet: bool, | 13 | pub is_snippet: bool, |
17 | } | 14 | } |
18 | 15 | ||
19 | impl SourceChange { | 16 | impl SourceChange { |
20 | /// Creates a new SourceChange with the given label | 17 | /// Creates a new SourceChange with the given label |
21 | /// from the edits. | 18 | /// from the edits. |
22 | pub fn from_edits<L: Into<String>>( | 19 | pub fn from_edits( |
23 | label: L, | ||
24 | source_file_edits: Vec<SourceFileEdit>, | 20 | source_file_edits: Vec<SourceFileEdit>, |
25 | file_system_edits: Vec<FileSystemEdit>, | 21 | file_system_edits: Vec<FileSystemEdit>, |
26 | ) -> Self { | 22 | ) -> Self { |
27 | SourceChange { | 23 | SourceChange { source_file_edits, file_system_edits, is_snippet: false } |
28 | label: label.into(), | ||
29 | source_file_edits, | ||
30 | file_system_edits, | ||
31 | cursor_position: None, | ||
32 | is_snippet: false, | ||
33 | } | ||
34 | } | 24 | } |
35 | 25 | ||
36 | /// Creates a new SourceChange with the given label, | 26 | /// Creates a new SourceChange with the given label, |
37 | /// containing only the given `SourceFileEdits`. | 27 | /// containing only the given `SourceFileEdits`. |
38 | pub fn source_file_edits<L: Into<String>>(label: L, edits: Vec<SourceFileEdit>) -> Self { | 28 | pub fn source_file_edits(edits: Vec<SourceFileEdit>) -> Self { |
39 | let label = label.into(); | 29 | SourceChange { source_file_edits: edits, file_system_edits: vec![], is_snippet: false } |
40 | assert!(label.starts_with(char::is_uppercase)); | ||
41 | SourceChange { | ||
42 | label: label, | ||
43 | source_file_edits: edits, | ||
44 | file_system_edits: vec![], | ||
45 | cursor_position: None, | ||
46 | is_snippet: false, | ||
47 | } | ||
48 | } | ||
49 | |||
50 | /// Creates a new SourceChange with the given label, | ||
51 | /// containing only the given `FileSystemEdits`. | ||
52 | pub(crate) fn file_system_edits<L: Into<String>>(label: L, edits: Vec<FileSystemEdit>) -> Self { | ||
53 | SourceChange { | ||
54 | label: label.into(), | ||
55 | source_file_edits: vec![], | ||
56 | file_system_edits: edits, | ||
57 | cursor_position: None, | ||
58 | is_snippet: false, | ||
59 | } | ||
60 | } | ||
61 | |||
62 | /// Creates a new SourceChange with the given label, | ||
63 | /// containing only a single `SourceFileEdit`. | ||
64 | pub fn source_file_edit<L: Into<String>>(label: L, edit: SourceFileEdit) -> Self { | ||
65 | SourceChange::source_file_edits(label, vec![edit]) | ||
66 | } | ||
67 | |||
68 | /// Creates a new SourceChange with the given label | ||
69 | /// from the given `FileId` and `TextEdit` | ||
70 | pub fn source_file_edit_from<L: Into<String>>( | ||
71 | label: L, | ||
72 | file_id: FileId, | ||
73 | edit: TextEdit, | ||
74 | ) -> Self { | ||
75 | SourceChange::source_file_edit(label, SourceFileEdit { file_id, edit }) | ||
76 | } | 30 | } |
77 | |||
78 | /// Creates a new SourceChange with the given label | 31 | /// Creates a new SourceChange with the given label |
79 | /// from the given `FileId` and `TextEdit` | 32 | /// from the given `FileId` and `TextEdit` |
80 | pub fn file_system_edit<L: Into<String>>(label: L, edit: FileSystemEdit) -> Self { | 33 | pub fn source_file_edit_from(file_id: FileId, edit: TextEdit) -> Self { |
81 | SourceChange::file_system_edits(label, vec![edit]) | 34 | SourceFileEdit { file_id, edit }.into() |
82 | } | ||
83 | |||
84 | /// Sets the cursor position to the given `FilePosition` | ||
85 | pub fn with_cursor(mut self, cursor_position: FilePosition) -> Self { | ||
86 | self.cursor_position = Some(cursor_position); | ||
87 | self | ||
88 | } | ||
89 | |||
90 | /// Sets the cursor position to the given `FilePosition` | ||
91 | pub fn with_cursor_opt(mut self, cursor_position: Option<FilePosition>) -> Self { | ||
92 | self.cursor_position = cursor_position; | ||
93 | self | ||
94 | } | 35 | } |
95 | } | 36 | } |
96 | 37 | ||
@@ -100,24 +41,27 @@ pub struct SourceFileEdit { | |||
100 | pub edit: TextEdit, | 41 | pub edit: TextEdit, |
101 | } | 42 | } |
102 | 43 | ||
44 | impl From<SourceFileEdit> for SourceChange { | ||
45 | fn from(edit: SourceFileEdit) -> SourceChange { | ||
46 | SourceChange { | ||
47 | source_file_edits: vec![edit], | ||
48 | file_system_edits: Vec::new(), | ||
49 | is_snippet: false, | ||
50 | } | ||
51 | } | ||
52 | } | ||
53 | |||
103 | #[derive(Debug, Clone)] | 54 | #[derive(Debug, Clone)] |
104 | pub enum FileSystemEdit { | 55 | pub enum FileSystemEdit { |
105 | CreateFile { source_root: SourceRootId, path: RelativePathBuf }, | 56 | CreateFile { source_root: SourceRootId, path: RelativePathBuf }, |
106 | MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf }, | 57 | MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf }, |
107 | } | 58 | } |
108 | 59 | ||
109 | pub struct SingleFileChange { | 60 | impl From<FileSystemEdit> for SourceChange { |
110 | pub label: String, | 61 | fn from(edit: FileSystemEdit) -> SourceChange { |
111 | pub edit: TextEdit, | ||
112 | } | ||
113 | |||
114 | impl SingleFileChange { | ||
115 | pub fn into_source_change(self, file_id: FileId) -> SourceChange { | ||
116 | SourceChange { | 62 | SourceChange { |
117 | label: self.label, | 63 | source_file_edits: Vec::new(), |
118 | source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }], | 64 | file_system_edits: vec![edit], |
119 | file_system_edits: Vec::new(), | ||
120 | cursor_position: None, | ||
121 | is_snippet: false, | 65 | is_snippet: false, |
122 | } | 66 | } |
123 | } | 67 | } |
diff --git a/crates/ra_text_edit/src/lib.rs b/crates/ra_text_edit/src/lib.rs index c4f945101..25554f583 100644 --- a/crates/ra_text_edit/src/lib.rs +++ b/crates/ra_text_edit/src/lib.rs | |||
@@ -3,6 +3,7 @@ | |||
3 | //! `rust-analyzer` never mutates text itself and only sends diffs to clients, | 3 | //! `rust-analyzer` never mutates text itself and only sends diffs to clients, |
4 | //! so `TextEdit` is the ultimate representation of the work done by | 4 | //! so `TextEdit` is the ultimate representation of the work done by |
5 | //! rust-analyzer. | 5 | //! rust-analyzer. |
6 | use std::{slice, vec}; | ||
6 | 7 | ||
7 | pub use text_size::{TextRange, TextSize}; | 8 | pub use text_size::{TextRange, TextSize}; |
8 | 9 | ||
@@ -16,7 +17,7 @@ pub struct Indel { | |||
16 | pub delete: TextRange, | 17 | pub delete: TextRange, |
17 | } | 18 | } |
18 | 19 | ||
19 | #[derive(Debug, Clone)] | 20 | #[derive(Default, Debug, Clone)] |
20 | pub struct TextEdit { | 21 | pub struct TextEdit { |
21 | indels: Vec<Indel>, | 22 | indels: Vec<Indel>, |
22 | } | 23 | } |
@@ -63,25 +64,24 @@ impl TextEdit { | |||
63 | builder.finish() | 64 | builder.finish() |
64 | } | 65 | } |
65 | 66 | ||
66 | pub(crate) fn from_indels(mut indels: Vec<Indel>) -> TextEdit { | 67 | pub fn len(&self) -> usize { |
67 | indels.sort_by_key(|a| (a.delete.start(), a.delete.end())); | 68 | self.indels.len() |
68 | for (a1, a2) in indels.iter().zip(indels.iter().skip(1)) { | ||
69 | assert!(a1.delete.end() <= a2.delete.start()) | ||
70 | } | ||
71 | TextEdit { indels } | ||
72 | } | 69 | } |
73 | 70 | ||
74 | pub fn is_empty(&self) -> bool { | 71 | pub fn is_empty(&self) -> bool { |
75 | self.indels.is_empty() | 72 | self.indels.is_empty() |
76 | } | 73 | } |
77 | 74 | ||
78 | // FXME: impl IntoIter instead | 75 | pub fn iter(&self) -> slice::Iter<'_, Indel> { |
79 | pub fn as_indels(&self) -> &[Indel] { | 76 | self.indels.iter() |
80 | &self.indels | 77 | } |
78 | |||
79 | pub fn into_iter(self) -> vec::IntoIter<Indel> { | ||
80 | self.indels.into_iter() | ||
81 | } | 81 | } |
82 | 82 | ||
83 | pub fn apply(&self, text: &mut String) { | 83 | pub fn apply(&self, text: &mut String) { |
84 | match self.indels.len() { | 84 | match self.len() { |
85 | 0 => return, | 85 | 0 => return, |
86 | 1 => { | 86 | 1 => { |
87 | self.indels[0].apply(text); | 87 | self.indels[0].apply(text); |
@@ -114,6 +114,17 @@ impl TextEdit { | |||
114 | *text = buf | 114 | *text = buf |
115 | } | 115 | } |
116 | 116 | ||
117 | pub fn union(&mut self, other: TextEdit) -> Result<(), TextEdit> { | ||
118 | // FIXME: can be done without allocating intermediate vector | ||
119 | let mut all = self.iter().chain(other.iter()).collect::<Vec<_>>(); | ||
120 | if !check_disjoint(&mut all) { | ||
121 | return Err(other); | ||
122 | } | ||
123 | self.indels.extend(other.indels); | ||
124 | assert!(check_disjoint(&mut self.indels)); | ||
125 | Ok(()) | ||
126 | } | ||
127 | |||
117 | pub fn apply_to_offset(&self, offset: TextSize) -> Option<TextSize> { | 128 | pub fn apply_to_offset(&self, offset: TextSize) -> Option<TextSize> { |
118 | let mut res = offset; | 129 | let mut res = offset; |
119 | for indel in self.indels.iter() { | 130 | for indel in self.indels.iter() { |
@@ -141,9 +152,19 @@ impl TextEditBuilder { | |||
141 | self.indels.push(Indel::insert(offset, text)) | 152 | self.indels.push(Indel::insert(offset, text)) |
142 | } | 153 | } |
143 | pub fn finish(self) -> TextEdit { | 154 | pub fn finish(self) -> TextEdit { |
144 | TextEdit::from_indels(self.indels) | 155 | let mut indels = self.indels; |
156 | assert!(check_disjoint(&mut indels)); | ||
157 | TextEdit { indels } | ||
145 | } | 158 | } |
146 | pub fn invalidates_offset(&self, offset: TextSize) -> bool { | 159 | pub fn invalidates_offset(&self, offset: TextSize) -> bool { |
147 | self.indels.iter().any(|indel| indel.delete.contains_inclusive(offset)) | 160 | self.indels.iter().any(|indel| indel.delete.contains_inclusive(offset)) |
148 | } | 161 | } |
149 | } | 162 | } |
163 | |||
164 | fn check_disjoint(indels: &mut [impl std::borrow::Borrow<Indel>]) -> bool { | ||
165 | indels.sort_by_key(|indel| (indel.borrow().delete.start(), indel.borrow().delete.end())); | ||
166 | indels | ||
167 | .iter() | ||
168 | .zip(indels.iter().skip(1)) | ||
169 | .all(|(l, r)| l.borrow().delete.end() <= r.borrow().delete.start()) | ||
170 | } | ||
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index 09908458d..e82fd57de 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs | |||
@@ -74,12 +74,25 @@ fn run_server() -> Result<()> { | |||
74 | log::info!("lifecycle: server started"); | 74 | log::info!("lifecycle: server started"); |
75 | 75 | ||
76 | let (connection, io_threads) = Connection::stdio(); | 76 | let (connection, io_threads) = Connection::stdio(); |
77 | let server_capabilities = serde_json::to_value(rust_analyzer::server_capabilities()).unwrap(); | ||
78 | 77 | ||
79 | let initialize_params = connection.initialize(server_capabilities)?; | 78 | let (initialize_id, initialize_params) = connection.initialize_start()?; |
80 | let initialize_params = | 79 | let initialize_params = |
81 | from_json::<lsp_types::InitializeParams>("InitializeParams", initialize_params)?; | 80 | from_json::<lsp_types::InitializeParams>("InitializeParams", initialize_params)?; |
82 | 81 | ||
82 | let server_capabilities = rust_analyzer::server_capabilities(&initialize_params.capabilities); | ||
83 | |||
84 | let initialize_result = lsp_types::InitializeResult { | ||
85 | capabilities: server_capabilities, | ||
86 | server_info: Some(lsp_types::ServerInfo { | ||
87 | name: String::from("rust-analyzer"), | ||
88 | version: Some(String::from(env!("REV"))), | ||
89 | }), | ||
90 | }; | ||
91 | |||
92 | let initialize_result = serde_json::to_value(initialize_result).unwrap(); | ||
93 | |||
94 | connection.initialize_finish(initialize_id, initialize_result)?; | ||
95 | |||
83 | if let Some(client_info) = initialize_params.client_info { | 96 | if let Some(client_info) = initialize_params.client_info { |
84 | log::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default()); | 97 | log::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default()); |
85 | } | 98 | } |
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs index 110c9a442..780fc9317 100644 --- a/crates/rust-analyzer/src/caps.rs +++ b/crates/rust-analyzer/src/caps.rs | |||
@@ -1,19 +1,23 @@ | |||
1 | //! Advertizes the capabilities of the LSP Server. | 1 | //! Advertizes the capabilities of the LSP Server. |
2 | use std::env; | 2 | use std::env; |
3 | 3 | ||
4 | use crate::semantic_tokens; | ||
5 | |||
6 | use lsp_types::{ | 4 | use lsp_types::{ |
7 | CallHierarchyServerCapability, CodeActionOptions, CodeActionProviderCapability, | 5 | CallHierarchyServerCapability, ClientCapabilities, CodeActionOptions, |
8 | CodeLensOptions, CompletionOptions, DocumentOnTypeFormattingOptions, | 6 | CodeActionProviderCapability, CodeLensOptions, CompletionOptions, |
9 | FoldingRangeProviderCapability, ImplementationProviderCapability, RenameOptions, | 7 | DocumentOnTypeFormattingOptions, FoldingRangeProviderCapability, |
10 | RenameProviderCapability, SaveOptions, SelectionRangeProviderCapability, | 8 | ImplementationProviderCapability, RenameOptions, RenameProviderCapability, SaveOptions, |
11 | SemanticTokensDocumentProvider, SemanticTokensLegend, SemanticTokensOptions, | 9 | SelectionRangeProviderCapability, SemanticTokensDocumentProvider, SemanticTokensLegend, |
12 | ServerCapabilities, SignatureHelpOptions, TextDocumentSyncCapability, TextDocumentSyncKind, | 10 | SemanticTokensOptions, ServerCapabilities, SignatureHelpOptions, TextDocumentSyncCapability, |
13 | TextDocumentSyncOptions, TypeDefinitionProviderCapability, WorkDoneProgressOptions, | 11 | TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability, |
12 | WorkDoneProgressOptions, | ||
14 | }; | 13 | }; |
14 | use serde_json::json; | ||
15 | |||
16 | use crate::semantic_tokens; | ||
17 | |||
18 | pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabilities { | ||
19 | let code_action_provider = code_action_capabilities(client_caps); | ||
15 | 20 | ||
16 | pub fn server_capabilities() -> ServerCapabilities { | ||
17 | ServerCapabilities { | 21 | ServerCapabilities { |
18 | text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions { | 22 | text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions { |
19 | open_close: Some(true), | 23 | open_close: Some(true), |
@@ -45,20 +49,7 @@ pub fn server_capabilities() -> ServerCapabilities { | |||
45 | document_highlight_provider: Some(true), | 49 | document_highlight_provider: Some(true), |
46 | document_symbol_provider: Some(true), | 50 | document_symbol_provider: Some(true), |
47 | workspace_symbol_provider: Some(true), | 51 | workspace_symbol_provider: Some(true), |
48 | code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions { | 52 | code_action_provider: Some(code_action_provider), |
49 | // Advertise support for all built-in CodeActionKinds | ||
50 | code_action_kinds: Some(vec![ | ||
51 | lsp_types::code_action_kind::EMPTY.to_string(), | ||
52 | lsp_types::code_action_kind::QUICKFIX.to_string(), | ||
53 | lsp_types::code_action_kind::REFACTOR.to_string(), | ||
54 | lsp_types::code_action_kind::REFACTOR_EXTRACT.to_string(), | ||
55 | lsp_types::code_action_kind::REFACTOR_INLINE.to_string(), | ||
56 | lsp_types::code_action_kind::REFACTOR_REWRITE.to_string(), | ||
57 | lsp_types::code_action_kind::SOURCE.to_string(), | ||
58 | lsp_types::code_action_kind::SOURCE_ORGANIZE_IMPORTS.to_string(), | ||
59 | ]), | ||
60 | work_done_progress_options: Default::default(), | ||
61 | })), | ||
62 | code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }), | 53 | code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }), |
63 | document_formatting_provider: Some(true), | 54 | document_formatting_provider: Some(true), |
64 | document_range_formatting_provider: None, | 55 | document_range_formatting_provider: None, |
@@ -91,6 +82,35 @@ pub fn server_capabilities() -> ServerCapabilities { | |||
91 | } | 82 | } |
92 | .into(), | 83 | .into(), |
93 | ), | 84 | ), |
94 | experimental: Default::default(), | 85 | experimental: Some(json!({ |
86 | "joinLines": true, | ||
87 | "ssr": true, | ||
88 | })), | ||
95 | } | 89 | } |
96 | } | 90 | } |
91 | |||
92 | fn code_action_capabilities(client_caps: &ClientCapabilities) -> CodeActionProviderCapability { | ||
93 | client_caps | ||
94 | .text_document | ||
95 | .as_ref() | ||
96 | .and_then(|it| it.code_action.as_ref()) | ||
97 | .and_then(|it| it.code_action_literal_support.as_ref()) | ||
98 | .map_or(CodeActionProviderCapability::Simple(true), |_| { | ||
99 | CodeActionProviderCapability::Options(CodeActionOptions { | ||
100 | // Advertise support for all built-in CodeActionKinds. | ||
101 | // Ideally we would base this off of the client capabilities | ||
102 | // but the client is supposed to fall back gracefully for unknown values. | ||
103 | code_action_kinds: Some(vec![ | ||
104 | lsp_types::code_action_kind::EMPTY.to_string(), | ||
105 | lsp_types::code_action_kind::QUICKFIX.to_string(), | ||
106 | lsp_types::code_action_kind::REFACTOR.to_string(), | ||
107 | lsp_types::code_action_kind::REFACTOR_EXTRACT.to_string(), | ||
108 | lsp_types::code_action_kind::REFACTOR_INLINE.to_string(), | ||
109 | lsp_types::code_action_kind::REFACTOR_REWRITE.to_string(), | ||
110 | lsp_types::code_action_kind::SOURCE.to_string(), | ||
111 | lsp_types::code_action_kind::SOURCE_ORGANIZE_IMPORTS.to_string(), | ||
112 | ]), | ||
113 | work_done_progress_options: Default::default(), | ||
114 | }) | ||
115 | }) | ||
116 | } | ||
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index d75c48597..c0f7c2c0c 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs | |||
@@ -102,6 +102,7 @@ pub struct ClientCapsConfig { | |||
102 | pub hierarchical_symbols: bool, | 102 | pub hierarchical_symbols: bool, |
103 | pub code_action_literals: bool, | 103 | pub code_action_literals: bool, |
104 | pub work_done_progress: bool, | 104 | pub work_done_progress: bool, |
105 | pub code_action_group: bool, | ||
105 | } | 106 | } |
106 | 107 | ||
107 | impl Default for Config { | 108 | impl Default for Config { |
@@ -268,10 +269,8 @@ impl Config { | |||
268 | { | 269 | { |
269 | self.client_caps.hierarchical_symbols = value | 270 | self.client_caps.hierarchical_symbols = value |
270 | } | 271 | } |
271 | if let Some(value) = doc_caps | 272 | if let Some(value) = |
272 | .code_action | 273 | doc_caps.code_action.as_ref().map(|it| it.code_action_literal_support.is_some()) |
273 | .as_ref() | ||
274 | .and_then(|it| Some(it.code_action_literal_support.is_some())) | ||
275 | { | 274 | { |
276 | self.client_caps.code_action_literals = value; | 275 | self.client_caps.code_action_literals = value; |
277 | } | 276 | } |
@@ -294,9 +293,13 @@ impl Config { | |||
294 | 293 | ||
295 | self.assist.allow_snippets(false); | 294 | self.assist.allow_snippets(false); |
296 | if let Some(experimental) = &caps.experimental { | 295 | if let Some(experimental) = &caps.experimental { |
297 | let enable = | 296 | let snippet_text_edit = |
298 | experimental.get("snippetTextEdit").and_then(|it| it.as_bool()) == Some(true); | 297 | experimental.get("snippetTextEdit").and_then(|it| it.as_bool()) == Some(true); |
299 | self.assist.allow_snippets(enable); | 298 | self.assist.allow_snippets(snippet_text_edit); |
299 | |||
300 | let code_action_group = | ||
301 | experimental.get("codeActionGroup").and_then(|it| it.as_bool()) == Some(true); | ||
302 | self.client_caps.code_action_group = code_action_group | ||
300 | } | 303 | } |
301 | } | 304 | } |
302 | } | 305 | } |
diff --git a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap index 96466b5c9..c40cfdcdc 100644 --- a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap +++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap | |||
@@ -65,6 +65,7 @@ expression: diag | |||
65 | fixes: [ | 65 | fixes: [ |
66 | CodeAction { | 66 | CodeAction { |
67 | title: "return the expression directly", | 67 | title: "return the expression directly", |
68 | group: None, | ||
68 | kind: Some( | 69 | kind: Some( |
69 | "quickfix", | 70 | "quickfix", |
70 | ), | 71 | ), |
diff --git a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap index 8f962277f..6dd3fcb2e 100644 --- a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap +++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap | |||
@@ -50,6 +50,7 @@ expression: diag | |||
50 | fixes: [ | 50 | fixes: [ |
51 | CodeAction { | 51 | CodeAction { |
52 | title: "consider prefixing with an underscore", | 52 | title: "consider prefixing with an underscore", |
53 | group: None, | ||
53 | kind: Some( | 54 | kind: Some( |
54 | "quickfix", | 55 | "quickfix", |
55 | ), | 56 | ), |
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs index afea59525..a500d670a 100644 --- a/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs | |||
@@ -145,6 +145,7 @@ fn map_rust_child_diagnostic( | |||
145 | } else { | 145 | } else { |
146 | MappedRustChildDiagnostic::SuggestedFix(lsp_ext::CodeAction { | 146 | MappedRustChildDiagnostic::SuggestedFix(lsp_ext::CodeAction { |
147 | title: rd.message.clone(), | 147 | title: rd.message.clone(), |
148 | group: None, | ||
148 | kind: Some("quickfix".to_string()), | 149 | kind: Some("quickfix".to_string()), |
149 | edit: Some(lsp_ext::SnippetWorkspaceEdit { | 150 | edit: Some(lsp_ext::SnippetWorkspaceEdit { |
150 | // FIXME: there's no good reason to use edit_map here.... | 151 | // FIXME: there's no good reason to use edit_map here.... |
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index f75a26eb7..c25d90a50 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs | |||
@@ -87,22 +87,22 @@ pub enum JoinLines {} | |||
87 | 87 | ||
88 | impl Request for JoinLines { | 88 | impl Request for JoinLines { |
89 | type Params = JoinLinesParams; | 89 | type Params = JoinLinesParams; |
90 | type Result = SourceChange; | 90 | type Result = Vec<lsp_types::TextEdit>; |
91 | const METHOD: &'static str = "rust-analyzer/joinLines"; | 91 | const METHOD: &'static str = "experimental/joinLines"; |
92 | } | 92 | } |
93 | 93 | ||
94 | #[derive(Deserialize, Serialize, Debug)] | 94 | #[derive(Deserialize, Serialize, Debug)] |
95 | #[serde(rename_all = "camelCase")] | 95 | #[serde(rename_all = "camelCase")] |
96 | pub struct JoinLinesParams { | 96 | pub struct JoinLinesParams { |
97 | pub text_document: TextDocumentIdentifier, | 97 | pub text_document: TextDocumentIdentifier, |
98 | pub range: Range, | 98 | pub ranges: Vec<Range>, |
99 | } | 99 | } |
100 | 100 | ||
101 | pub enum OnEnter {} | 101 | pub enum OnEnter {} |
102 | 102 | ||
103 | impl Request for OnEnter { | 103 | impl Request for OnEnter { |
104 | type Params = lsp_types::TextDocumentPositionParams; | 104 | type Params = lsp_types::TextDocumentPositionParams; |
105 | type Result = Option<SourceChange>; | 105 | type Result = Option<SnippetWorkspaceEdit>; |
106 | const METHOD: &'static str = "rust-analyzer/onEnter"; | 106 | const METHOD: &'static str = "rust-analyzer/onEnter"; |
107 | } | 107 | } |
108 | 108 | ||
@@ -133,14 +133,6 @@ pub struct Runnable { | |||
133 | pub cwd: Option<PathBuf>, | 133 | pub cwd: Option<PathBuf>, |
134 | } | 134 | } |
135 | 135 | ||
136 | #[derive(Deserialize, Serialize, Debug)] | ||
137 | #[serde(rename_all = "camelCase")] | ||
138 | pub struct SourceChange { | ||
139 | pub label: String, | ||
140 | pub workspace_edit: SnippetWorkspaceEdit, | ||
141 | pub cursor_position: Option<lsp_types::TextDocumentPositionParams>, | ||
142 | } | ||
143 | |||
144 | pub enum InlayHints {} | 136 | pub enum InlayHints {} |
145 | 137 | ||
146 | impl Request for InlayHints { | 138 | impl Request for InlayHints { |
@@ -173,8 +165,8 @@ pub enum Ssr {} | |||
173 | 165 | ||
174 | impl Request for Ssr { | 166 | impl Request for Ssr { |
175 | type Params = SsrParams; | 167 | type Params = SsrParams; |
176 | type Result = SourceChange; | 168 | type Result = lsp_types::WorkspaceEdit; |
177 | const METHOD: &'static str = "rust-analyzer/ssr"; | 169 | const METHOD: &'static str = "experimental/ssr"; |
178 | } | 170 | } |
179 | 171 | ||
180 | #[derive(Debug, Deserialize, Serialize)] | 172 | #[derive(Debug, Deserialize, Serialize)] |
@@ -196,6 +188,8 @@ impl Request for CodeActionRequest { | |||
196 | pub struct CodeAction { | 188 | pub struct CodeAction { |
197 | pub title: String, | 189 | pub title: String, |
198 | #[serde(skip_serializing_if = "Option::is_none")] | 190 | #[serde(skip_serializing_if = "Option::is_none")] |
191 | pub group: Option<String>, | ||
192 | #[serde(skip_serializing_if = "Option::is_none")] | ||
199 | pub kind: Option<String>, | 193 | pub kind: Option<String>, |
200 | #[serde(skip_serializing_if = "Option::is_none")] | 194 | #[serde(skip_serializing_if = "Option::is_none")] |
201 | pub command: Option<lsp_types::Command>, | 195 | pub command: Option<lsp_types::Command>, |
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index be6a0aece..ba6857556 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs | |||
@@ -15,10 +15,11 @@ use lsp_types::{ | |||
15 | DocumentSymbol, FoldingRange, FoldingRangeParams, Hover, HoverContents, Location, | 15 | DocumentSymbol, FoldingRange, FoldingRangeParams, Hover, HoverContents, Location, |
16 | MarkupContent, MarkupKind, Position, PrepareRenameResponse, Range, RenameParams, | 16 | MarkupContent, MarkupKind, Position, PrepareRenameResponse, Range, RenameParams, |
17 | SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult, | 17 | SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult, |
18 | SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, TextEdit, Url, WorkspaceEdit, | 18 | SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, Url, WorkspaceEdit, |
19 | }; | 19 | }; |
20 | use ra_ide::{ | 20 | use ra_ide::{ |
21 | Assist, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, SearchScope, | 21 | FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, SearchScope, |
22 | TextEdit, | ||
22 | }; | 23 | }; |
23 | use ra_prof::profile; | 24 | use ra_prof::profile; |
24 | use ra_project_model::TargetKind; | 25 | use ra_project_model::TargetKind; |
@@ -149,22 +150,35 @@ pub fn handle_find_matching_brace( | |||
149 | pub fn handle_join_lines( | 150 | pub fn handle_join_lines( |
150 | world: WorldSnapshot, | 151 | world: WorldSnapshot, |
151 | params: lsp_ext::JoinLinesParams, | 152 | params: lsp_ext::JoinLinesParams, |
152 | ) -> Result<lsp_ext::SourceChange> { | 153 | ) -> Result<Vec<lsp_types::TextEdit>> { |
153 | let _p = profile("handle_join_lines"); | 154 | let _p = profile("handle_join_lines"); |
154 | let frange = from_proto::file_range(&world, params.text_document, params.range)?; | 155 | let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; |
155 | let source_change = world.analysis().join_lines(frange)?; | 156 | let line_index = world.analysis().file_line_index(file_id)?; |
156 | to_proto::source_change(&world, source_change) | 157 | let line_endings = world.file_line_endings(file_id); |
158 | let mut res = TextEdit::default(); | ||
159 | for range in params.ranges { | ||
160 | let range = from_proto::text_range(&line_index, range); | ||
161 | let edit = world.analysis().join_lines(FileRange { file_id, range })?; | ||
162 | match res.union(edit) { | ||
163 | Ok(()) => (), | ||
164 | Err(_edit) => { | ||
165 | // just ignore overlapping edits | ||
166 | } | ||
167 | } | ||
168 | } | ||
169 | let res = to_proto::text_edit_vec(&line_index, line_endings, res); | ||
170 | Ok(res) | ||
157 | } | 171 | } |
158 | 172 | ||
159 | pub fn handle_on_enter( | 173 | pub fn handle_on_enter( |
160 | world: WorldSnapshot, | 174 | world: WorldSnapshot, |
161 | params: lsp_types::TextDocumentPositionParams, | 175 | params: lsp_types::TextDocumentPositionParams, |
162 | ) -> Result<Option<lsp_ext::SourceChange>> { | 176 | ) -> Result<Option<lsp_ext::SnippetWorkspaceEdit>> { |
163 | let _p = profile("handle_on_enter"); | 177 | let _p = profile("handle_on_enter"); |
164 | let position = from_proto::file_position(&world, params)?; | 178 | let position = from_proto::file_position(&world, params)?; |
165 | match world.analysis().on_enter(position)? { | 179 | match world.analysis().on_enter(position)? { |
166 | None => Ok(None), | 180 | None => Ok(None), |
167 | Some(source_change) => to_proto::source_change(&world, source_change).map(Some), | 181 | Some(source_change) => to_proto::snippet_workspace_edit(&world, source_change).map(Some), |
168 | } | 182 | } |
169 | } | 183 | } |
170 | 184 | ||
@@ -172,7 +186,7 @@ pub fn handle_on_enter( | |||
172 | pub fn handle_on_type_formatting( | 186 | pub fn handle_on_type_formatting( |
173 | world: WorldSnapshot, | 187 | world: WorldSnapshot, |
174 | params: lsp_types::DocumentOnTypeFormattingParams, | 188 | params: lsp_types::DocumentOnTypeFormattingParams, |
175 | ) -> Result<Option<Vec<TextEdit>>> { | 189 | ) -> Result<Option<Vec<lsp_types::TextEdit>>> { |
176 | let _p = profile("handle_on_type_formatting"); | 190 | let _p = profile("handle_on_type_formatting"); |
177 | let mut position = from_proto::file_position(&world, params.text_document_position)?; | 191 | let mut position = from_proto::file_position(&world, params.text_document_position)?; |
178 | let line_index = world.analysis().file_line_index(position.file_id)?; | 192 | let line_index = world.analysis().file_line_index(position.file_id)?; |
@@ -267,7 +281,7 @@ pub fn handle_document_symbol( | |||
267 | kind: symbol.kind, | 281 | kind: symbol.kind, |
268 | deprecated: symbol.deprecated, | 282 | deprecated: symbol.deprecated, |
269 | location: Location::new(url.clone(), symbol.range), | 283 | location: Location::new(url.clone(), symbol.range), |
270 | container_name: container_name, | 284 | container_name, |
271 | }); | 285 | }); |
272 | 286 | ||
273 | for child in symbol.children.iter().flatten() { | 287 | for child in symbol.children.iter().flatten() { |
@@ -618,7 +632,7 @@ pub fn handle_references( | |||
618 | pub fn handle_formatting( | 632 | pub fn handle_formatting( |
619 | world: WorldSnapshot, | 633 | world: WorldSnapshot, |
620 | params: DocumentFormattingParams, | 634 | params: DocumentFormattingParams, |
621 | ) -> Result<Option<Vec<TextEdit>>> { | 635 | ) -> Result<Option<Vec<lsp_types::TextEdit>>> { |
622 | let _p = profile("handle_formatting"); | 636 | let _p = profile("handle_formatting"); |
623 | let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; | 637 | let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; |
624 | let file = world.analysis().file_text(file_id)?; | 638 | let file = world.analysis().file_text(file_id)?; |
@@ -685,7 +699,7 @@ pub fn handle_formatting( | |||
685 | } | 699 | } |
686 | } | 700 | } |
687 | 701 | ||
688 | Ok(Some(vec![TextEdit { | 702 | Ok(Some(vec![lsp_types::TextEdit { |
689 | range: Range::new(Position::new(0, 0), end_position), | 703 | range: Range::new(Position::new(0, 0), end_position), |
690 | new_text: captured_stdout, | 704 | new_text: captured_stdout, |
691 | }])) | 705 | }])) |
@@ -706,6 +720,7 @@ pub fn handle_code_action( | |||
706 | let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; | 720 | let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; |
707 | let line_index = world.analysis().file_line_index(file_id)?; | 721 | let line_index = world.analysis().file_line_index(file_id)?; |
708 | let range = from_proto::text_range(&line_index, params.range); | 722 | let range = from_proto::text_range(&line_index, params.range); |
723 | let frange = FileRange { file_id, range }; | ||
709 | 724 | ||
710 | let diagnostics = world.analysis().diagnostics(file_id)?; | 725 | let diagnostics = world.analysis().diagnostics(file_id)?; |
711 | let mut res: Vec<lsp_ext::CodeAction> = Vec::new(); | 726 | let mut res: Vec<lsp_ext::CodeAction> = Vec::new(); |
@@ -716,10 +731,11 @@ pub fn handle_code_action( | |||
716 | .filter(|(diag_range, _fix)| diag_range.intersect(range).is_some()) | 731 | .filter(|(diag_range, _fix)| diag_range.intersect(range).is_some()) |
717 | .map(|(_range, fix)| fix); | 732 | .map(|(_range, fix)| fix); |
718 | 733 | ||
719 | for source_edit in fixes_from_diagnostics { | 734 | for fix in fixes_from_diagnostics { |
720 | let title = source_edit.label.clone(); | 735 | let title = fix.label; |
721 | let edit = to_proto::snippet_workspace_edit(&world, source_edit)?; | 736 | let edit = to_proto::snippet_workspace_edit(&world, fix.source_change)?; |
722 | let action = lsp_ext::CodeAction { title, kind: None, edit: Some(edit), command: None }; | 737 | let action = |
738 | lsp_ext::CodeAction { title, group: None, kind: None, edit: Some(edit), command: None }; | ||
723 | res.push(action); | 739 | res.push(action); |
724 | } | 740 | } |
725 | 741 | ||
@@ -731,53 +747,9 @@ pub fn handle_code_action( | |||
731 | res.push(fix.action.clone()); | 747 | res.push(fix.action.clone()); |
732 | } | 748 | } |
733 | 749 | ||
734 | let mut grouped_assists: FxHashMap<String, (usize, Vec<Assist>)> = FxHashMap::default(); | 750 | for assist in world.analysis().assists(&world.config.assist, frange)?.into_iter() { |
735 | for assist in | 751 | res.push(to_proto::code_action(&world, assist)?.into()); |
736 | world.analysis().assists(&world.config.assist, FileRange { file_id, range })?.into_iter() | ||
737 | { | ||
738 | match &assist.group_label { | ||
739 | Some(label) => grouped_assists | ||
740 | .entry(label.to_owned()) | ||
741 | .or_insert_with(|| { | ||
742 | let idx = res.len(); | ||
743 | let dummy = lsp_ext::CodeAction { | ||
744 | title: String::new(), | ||
745 | kind: None, | ||
746 | command: None, | ||
747 | edit: None, | ||
748 | }; | ||
749 | res.push(dummy); | ||
750 | (idx, Vec::new()) | ||
751 | }) | ||
752 | .1 | ||
753 | .push(assist), | ||
754 | None => { | ||
755 | res.push(to_proto::code_action(&world, assist)?.into()); | ||
756 | } | ||
757 | } | ||
758 | } | ||
759 | |||
760 | for (group_label, (idx, assists)) in grouped_assists { | ||
761 | if assists.len() == 1 { | ||
762 | res[idx] = to_proto::code_action(&world, assists.into_iter().next().unwrap())?.into(); | ||
763 | } else { | ||
764 | let title = group_label; | ||
765 | |||
766 | let mut arguments = Vec::with_capacity(assists.len()); | ||
767 | for assist in assists { | ||
768 | let source_change = to_proto::source_change(&world, assist.source_change)?; | ||
769 | arguments.push(to_value(source_change)?); | ||
770 | } | ||
771 | |||
772 | let command = Some(Command { | ||
773 | title: title.clone(), | ||
774 | command: "rust-analyzer.selectAndApplySourceChange".to_string(), | ||
775 | arguments: Some(vec![serde_json::Value::Array(arguments)]), | ||
776 | }); | ||
777 | res[idx] = lsp_ext::CodeAction { title, kind: None, edit: None, command }; | ||
778 | } | ||
779 | } | 752 | } |
780 | |||
781 | Ok(Some(res)) | 753 | Ok(Some(res)) |
782 | } | 754 | } |
783 | 755 | ||
@@ -972,11 +944,11 @@ pub fn handle_document_highlight( | |||
972 | pub fn handle_ssr( | 944 | pub fn handle_ssr( |
973 | world: WorldSnapshot, | 945 | world: WorldSnapshot, |
974 | params: lsp_ext::SsrParams, | 946 | params: lsp_ext::SsrParams, |
975 | ) -> Result<lsp_ext::SourceChange> { | 947 | ) -> Result<lsp_types::WorkspaceEdit> { |
976 | let _p = profile("handle_ssr"); | 948 | let _p = profile("handle_ssr"); |
977 | let source_change = | 949 | let source_change = |
978 | world.analysis().structural_search_replace(¶ms.query, params.parse_only)??; | 950 | world.analysis().structural_search_replace(¶ms.query, params.parse_only)??; |
979 | to_proto::source_change(&world, source_change) | 951 | to_proto::workspace_edit(&world, source_change) |
980 | } | 952 | } |
981 | 953 | ||
982 | pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> { | 954 | pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> { |
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index af54f81b7..7dd7d9416 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -1,13 +1,12 @@ | |||
1 | //! Conversion of rust-analyzer specific types to lsp_types equivalents. | 1 | //! Conversion of rust-analyzer specific types to lsp_types equivalents. |
2 | use ra_db::{FileId, FileRange}; | 2 | use ra_db::{FileId, FileRange}; |
3 | use ra_ide::{ | 3 | use ra_ide::{ |
4 | translate_offset_with_edit, Assist, CompletionItem, CompletionItemKind, Documentation, | 4 | Assist, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold, FoldKind, |
5 | FileSystemEdit, Fold, FoldKind, FunctionSignature, Highlight, HighlightModifier, HighlightTag, | 5 | FunctionSignature, Highlight, HighlightModifier, HighlightTag, HighlightedRange, Indel, |
6 | HighlightedRange, InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, | 6 | InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, ReferenceAccess, Severity, |
7 | ReferenceAccess, Severity, SourceChange, SourceFileEdit, | 7 | SourceChange, SourceFileEdit, TextEdit, |
8 | }; | 8 | }; |
9 | use ra_syntax::{SyntaxKind, TextRange, TextSize}; | 9 | use ra_syntax::{SyntaxKind, TextRange, TextSize}; |
10 | use ra_text_edit::{Indel, TextEdit}; | ||
11 | use ra_vfs::LineEndings; | 10 | use ra_vfs::LineEndings; |
12 | 11 | ||
13 | use crate::{lsp_ext, semantic_tokens, world::WorldSnapshot, Result}; | 12 | use crate::{lsp_ext, semantic_tokens, world::WorldSnapshot, Result}; |
@@ -133,11 +132,7 @@ pub(crate) fn text_edit_vec( | |||
133 | line_endings: LineEndings, | 132 | line_endings: LineEndings, |
134 | text_edit: TextEdit, | 133 | text_edit: TextEdit, |
135 | ) -> Vec<lsp_types::TextEdit> { | 134 | ) -> Vec<lsp_types::TextEdit> { |
136 | text_edit | 135 | text_edit.into_iter().map(|indel| self::text_edit(line_index, line_endings, indel)).collect() |
137 | .as_indels() | ||
138 | .iter() | ||
139 | .map(|it| self::text_edit(line_index, line_endings, it.clone())) | ||
140 | .collect() | ||
141 | } | 136 | } |
142 | 137 | ||
143 | pub(crate) fn completion_item( | 138 | pub(crate) fn completion_item( |
@@ -150,7 +145,7 @@ pub(crate) fn completion_item( | |||
150 | // LSP does not allow arbitrary edits in completion, so we have to do a | 145 | // LSP does not allow arbitrary edits in completion, so we have to do a |
151 | // non-trivial mapping here. | 146 | // non-trivial mapping here. |
152 | let source_range = completion_item.source_range(); | 147 | let source_range = completion_item.source_range(); |
153 | for indel in completion_item.text_edit().as_indels() { | 148 | for indel in completion_item.text_edit().iter() { |
154 | if indel.delete.contains_range(source_range) { | 149 | if indel.delete.contains_range(source_range) { |
155 | text_edit = Some(if indel.delete == source_range { | 150 | text_edit = Some(if indel.delete == source_range { |
156 | self::text_edit(line_index, line_endings, indel.clone()) | 151 | self::text_edit(line_index, line_endings, indel.clone()) |
@@ -375,14 +370,6 @@ pub(crate) fn url(world: &WorldSnapshot, file_id: FileId) -> Result<lsp_types::U | |||
375 | world.file_id_to_uri(file_id) | 370 | world.file_id_to_uri(file_id) |
376 | } | 371 | } |
377 | 372 | ||
378 | pub(crate) fn text_document_identifier( | ||
379 | world: &WorldSnapshot, | ||
380 | file_id: FileId, | ||
381 | ) -> Result<lsp_types::TextDocumentIdentifier> { | ||
382 | let res = lsp_types::TextDocumentIdentifier { uri: url(world, file_id)? }; | ||
383 | Ok(res) | ||
384 | } | ||
385 | |||
386 | pub(crate) fn versioned_text_document_identifier( | 373 | pub(crate) fn versioned_text_document_identifier( |
387 | world: &WorldSnapshot, | 374 | world: &WorldSnapshot, |
388 | file_id: FileId, | 375 | file_id: FileId, |
@@ -467,9 +454,8 @@ pub(crate) fn snippet_text_document_edit( | |||
467 | let line_endings = world.file_line_endings(source_file_edit.file_id); | 454 | let line_endings = world.file_line_endings(source_file_edit.file_id); |
468 | let edits = source_file_edit | 455 | let edits = source_file_edit |
469 | .edit | 456 | .edit |
470 | .as_indels() | 457 | .into_iter() |
471 | .iter() | 458 | .map(|it| snippet_text_edit(&line_index, line_endings, is_snippet, it)) |
472 | .map(|it| snippet_text_edit(&line_index, line_endings, is_snippet, it.clone())) | ||
473 | .collect(); | 459 | .collect(); |
474 | Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits }) | 460 | Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits }) |
475 | } | 461 | } |
@@ -492,36 +478,6 @@ pub(crate) fn resource_op( | |||
492 | Ok(res) | 478 | Ok(res) |
493 | } | 479 | } |
494 | 480 | ||
495 | pub(crate) fn source_change( | ||
496 | world: &WorldSnapshot, | ||
497 | source_change: SourceChange, | ||
498 | ) -> Result<lsp_ext::SourceChange> { | ||
499 | let cursor_position = match source_change.cursor_position { | ||
500 | None => None, | ||
501 | Some(pos) => { | ||
502 | let line_index = world.analysis().file_line_index(pos.file_id)?; | ||
503 | let edit = source_change | ||
504 | .source_file_edits | ||
505 | .iter() | ||
506 | .find(|it| it.file_id == pos.file_id) | ||
507 | .map(|it| &it.edit); | ||
508 | let line_col = match edit { | ||
509 | Some(edit) => translate_offset_with_edit(&*line_index, pos.offset, edit), | ||
510 | None => line_index.line_col(pos.offset), | ||
511 | }; | ||
512 | let position = | ||
513 | lsp_types::Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16)); | ||
514 | Some(lsp_types::TextDocumentPositionParams { | ||
515 | text_document: text_document_identifier(world, pos.file_id)?, | ||
516 | position, | ||
517 | }) | ||
518 | } | ||
519 | }; | ||
520 | let label = source_change.label.clone(); | ||
521 | let workspace_edit = self::snippet_workspace_edit(world, source_change)?; | ||
522 | Ok(lsp_ext::SourceChange { label, workspace_edit, cursor_position }) | ||
523 | } | ||
524 | |||
525 | pub(crate) fn snippet_workspace_edit( | 481 | pub(crate) fn snippet_workspace_edit( |
526 | world: &WorldSnapshot, | 482 | world: &WorldSnapshot, |
527 | source_change: SourceChange, | 483 | source_change: SourceChange, |
@@ -639,25 +595,12 @@ fn main() <fold>{ | |||
639 | } | 595 | } |
640 | 596 | ||
641 | pub(crate) fn code_action(world: &WorldSnapshot, assist: Assist) -> Result<lsp_ext::CodeAction> { | 597 | pub(crate) fn code_action(world: &WorldSnapshot, assist: Assist) -> Result<lsp_ext::CodeAction> { |
642 | let res = if assist.source_change.cursor_position.is_none() { | 598 | let res = lsp_ext::CodeAction { |
643 | lsp_ext::CodeAction { | 599 | title: assist.label, |
644 | title: assist.label, | 600 | group: if world.config.client_caps.code_action_group { assist.group_label } else { None }, |
645 | kind: Some(String::new()), | 601 | kind: Some(String::new()), |
646 | edit: Some(snippet_workspace_edit(world, assist.source_change)?), | 602 | edit: Some(snippet_workspace_edit(world, assist.source_change)?), |
647 | command: None, | 603 | command: None, |
648 | } | ||
649 | } else { | ||
650 | assert!(!assist.source_change.is_snippet); | ||
651 | let source_change = source_change(&world, assist.source_change)?; | ||
652 | let arg = serde_json::to_value(source_change)?; | ||
653 | let title = assist.label; | ||
654 | let command = lsp_types::Command { | ||
655 | title: title.clone(), | ||
656 | command: "rust-analyzer.applySourceChange".to_string(), | ||
657 | arguments: Some(vec![arg]), | ||
658 | }; | ||
659 | |||
660 | lsp_ext::CodeAction { title, kind: Some(String::new()), edit: None, command: Some(command) } | ||
661 | }; | 604 | }; |
662 | Ok(res) | 605 | Ok(res) |
663 | } | 606 | } |
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs index 74676b3ee..738a9a8e3 100644 --- a/crates/rust-analyzer/tests/heavy_tests/main.rs +++ b/crates/rust-analyzer/tests/heavy_tests/main.rs | |||
@@ -474,27 +474,21 @@ fn main() {{}} | |||
474 | position: Position { line: 0, character: 5 }, | 474 | position: Position { line: 0, character: 5 }, |
475 | }, | 475 | }, |
476 | json!({ | 476 | json!({ |
477 | "cursorPosition": { | 477 | "documentChanges": [ |
478 | "position": { "character": 4, "line": 1 }, | 478 | { |
479 | "textDocument": { "uri": "file:///[..]src/m0.rs" } | 479 | "edits": [ |
480 | }, | 480 | { |
481 | "label": "On enter", | 481 | "insertTextFormat": 2, |
482 | "workspaceEdit": { | 482 | "newText": "\n/// $0", |
483 | "documentChanges": [ | 483 | "range": { |
484 | { | 484 | "end": { "character": 5, "line": 0 }, |
485 | "edits": [ | 485 | "start": { "character": 5, "line": 0 } |
486 | { | ||
487 | "newText": "\n/// ", | ||
488 | "range": { | ||
489 | "end": { "character": 5, "line": 0 }, | ||
490 | "start": { "character": 5, "line": 0 } | ||
491 | } | ||
492 | } | 486 | } |
493 | ], | 487 | } |
494 | "textDocument": { "uri": "file:///[..]src/m0.rs", "version": null } | 488 | ], |
495 | } | 489 | "textDocument": { "uri": "file:///[..]src/m0.rs", "version": null } |
496 | ] | 490 | } |
497 | } | 491 | ] |
498 | }), | 492 | }), |
499 | ); | 493 | ); |
500 | let elapsed = start.elapsed(); | 494 | let elapsed = start.elapsed(); |
@@ -526,27 +520,21 @@ version = \"0.0.0\" | |||
526 | position: Position { line: 0, character: 8 }, | 520 | position: Position { line: 0, character: 8 }, |
527 | }, | 521 | }, |
528 | json!({ | 522 | json!({ |
529 | "cursorPosition": { | 523 | "documentChanges": [ |
530 | "position": { "line": 1, "character": 4 }, | 524 | { |
531 | "textDocument": { "uri": "file:///[..]src/main.rs" } | 525 | "edits": [ |
532 | }, | 526 | { |
533 | "label": "On enter", | 527 | "insertTextFormat": 2, |
534 | "workspaceEdit": { | 528 | "newText": "\r\n/// $0", |
535 | "documentChanges": [ | 529 | "range": { |
536 | { | 530 | "end": { "line": 0, "character": 8 }, |
537 | "edits": [ | 531 | "start": { "line": 0, "character": 8 } |
538 | { | ||
539 | "newText": "\r\n/// ", | ||
540 | "range": { | ||
541 | "end": { "line": 0, "character": 8 }, | ||
542 | "start": { "line": 0, "character": 8 } | ||
543 | } | ||
544 | } | 532 | } |
545 | ], | 533 | } |
546 | "textDocument": { "uri": "file:///[..]src/main.rs", "version": null } | 534 | ], |
547 | } | 535 | "textDocument": { "uri": "file:///[..]src/main.rs", "version": null } |
548 | ] | 536 | } |
549 | } | 537 | ] |
550 | }), | 538 | }), |
551 | ); | 539 | ); |
552 | } | 540 | } |
@@ -786,5 +774,5 @@ pub fn foo(_input: TokenStream) -> TokenStream { | |||
786 | }); | 774 | }); |
787 | 775 | ||
788 | let value = res.get("contents").unwrap().get("value").unwrap().to_string(); | 776 | let value = res.get("contents").unwrap().get("value").unwrap().to_string(); |
789 | assert_eq!(value, r#""```rust\nfoo::Bar\nfn bar()\n```""#) | 777 | assert_eq!(value, r#""foo::Bar\n___\n\n```rust\nfn bar()\n```""#) |
790 | } | 778 | } |
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index d2ec6c021..158d3c599 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md | |||
@@ -5,15 +5,11 @@ It's a best effort document, when in doubt, consult the source (and send a PR wi | |||
5 | We aim to upstream all non Rust-specific extensions to the protocol, but this is not a top priority. | 5 | We aim to upstream all non Rust-specific extensions to the protocol, but this is not a top priority. |
6 | All capabilities are enabled via `experimental` field of `ClientCapabilities`. | 6 | All capabilities are enabled via `experimental` field of `ClientCapabilities`. |
7 | 7 | ||
8 | ## `SnippetTextEdit` | 8 | ## Snippet `TextEdit` |
9 | 9 | ||
10 | **Capability** | 10 | **Issue:** https://github.com/microsoft/language-server-protocol/issues/724 |
11 | 11 | ||
12 | ```typescript | 12 | **Client Capability:** `{ "snippetTextEdit": boolean }` |
13 | { | ||
14 | "snippetTextEdit": boolean | ||
15 | } | ||
16 | ``` | ||
17 | 13 | ||
18 | If this capability is set, `WorkspaceEdit`s returned from `codeAction` requests might contain `SnippetTextEdit`s instead of usual `TextEdit`s: | 14 | If this capability is set, `WorkspaceEdit`s returned from `codeAction` requests might contain `SnippetTextEdit`s instead of usual `TextEdit`s: |
19 | 15 | ||
@@ -32,3 +28,145 @@ export interface TextDocumentEdit { | |||
32 | 28 | ||
33 | When applying such code action, the editor should insert snippet, with tab stops and placeholder. | 29 | When applying such code action, the editor should insert snippet, with tab stops and placeholder. |
34 | At the moment, rust-analyzer guarantees that only a single edit will have `InsertTextFormat.Snippet`. | 30 | At the moment, rust-analyzer guarantees that only a single edit will have `InsertTextFormat.Snippet`. |
31 | |||
32 | ### Example | ||
33 | |||
34 | "Add `derive`" code action transforms `struct S;` into `#[derive($0)] struct S;` | ||
35 | |||
36 | ### Unresolved Questions | ||
37 | |||
38 | * Where exactly are `SnippetTextEdit`s allowed (only in code actions at the moment)? | ||
39 | * Can snippets span multiple files (so far, no)? | ||
40 | |||
41 | ## Join Lines | ||
42 | |||
43 | **Issue:** https://github.com/microsoft/language-server-protocol/issues/992 | ||
44 | |||
45 | **Server Capability:** `{ "joinLines": boolean }` | ||
46 | |||
47 | This request is send from client to server to handle "Join Lines" editor action. | ||
48 | |||
49 | **Method:** `experimental/JoinLines` | ||
50 | |||
51 | **Request:** | ||
52 | |||
53 | ```typescript | ||
54 | interface JoinLinesParams { | ||
55 | textDocument: TextDocumentIdentifier, | ||
56 | /// Currently active selections/cursor offsets. | ||
57 | /// This is an array to support multiple cursors. | ||
58 | ranges: Range[], | ||
59 | } | ||
60 | ``` | ||
61 | |||
62 | **Response:** | ||
63 | |||
64 | ```typescript | ||
65 | TextEdit[] | ||
66 | ``` | ||
67 | |||
68 | ### Example | ||
69 | |||
70 | ```rust | ||
71 | fn main() { | ||
72 | /*cursor here*/let x = { | ||
73 | 92 | ||
74 | }; | ||
75 | } | ||
76 | ``` | ||
77 | |||
78 | `experimental/joinLines` yields (curly braces are automagiacally removed) | ||
79 | |||
80 | ```rust | ||
81 | fn main() { | ||
82 | let x = 92; | ||
83 | } | ||
84 | ``` | ||
85 | |||
86 | ### Unresolved Question | ||
87 | |||
88 | * What is the position of the cursor after `joinLines`? | ||
89 | Currently this is left to editor's discretion, but it might be useful to specify on the server via snippets. | ||
90 | However, it then becomes unclear how it works with multi cursor. | ||
91 | |||
92 | ## Structural Search Replace (SSR) | ||
93 | |||
94 | **Server Capability:** `{ "ssr": boolean }` | ||
95 | |||
96 | This request is send from client to server to handle structural search replace -- automated syntax tree based transformation of the source. | ||
97 | |||
98 | **Method:** `experimental/ssr` | ||
99 | |||
100 | **Request:** | ||
101 | |||
102 | ```typescript | ||
103 | interface SsrParams { | ||
104 | /// Search query. | ||
105 | /// The specific syntax is specified outside of the protocol. | ||
106 | query: string, | ||
107 | /// If true, only check the syntax of the query and don't compute the actual edit. | ||
108 | parseOnly: bool, | ||
109 | } | ||
110 | ``` | ||
111 | |||
112 | **Response:** | ||
113 | |||
114 | ```typescript | ||
115 | WorkspaceEdit | ||
116 | ``` | ||
117 | |||
118 | ### Example | ||
119 | |||
120 | SSR with query `foo($a:expr, $b:expr) ==>> ($a).foo($b)` will transform, eg `foo(y + 5, z)` into `(y + 5).foo(z)`. | ||
121 | |||
122 | ### Unresolved Question | ||
123 | |||
124 | * Probably needs search without replace mode | ||
125 | * Needs a way to limit the scope to certain files. | ||
126 | |||
127 | ## `CodeAction` Groups | ||
128 | |||
129 | **Issue:** https://github.com/microsoft/language-server-protocol/issues/994 | ||
130 | |||
131 | **Client Capability:** `{ "codeActionGroup": boolean }` | ||
132 | |||
133 | If this capability is set, `CodeAction` returned from the server contain an additional field, `group`: | ||
134 | |||
135 | ```typescript | ||
136 | interface CodeAction { | ||
137 | title: string; | ||
138 | group?: string; | ||
139 | ... | ||
140 | } | ||
141 | ``` | ||
142 | |||
143 | All code-actions with the same `group` should be grouped under single (extendable) entry in lightbulb menu. | ||
144 | The set of actions `[ { title: "foo" }, { group: "frobnicate", title: "bar" }, { group: "frobnicate", title: "baz" }]` should be rendered as | ||
145 | |||
146 | ``` | ||
147 | 💡 | ||
148 | +-------------+ | ||
149 | | foo | | ||
150 | +-------------+-----+ | ||
151 | | frobnicate >| bar | | ||
152 | +-------------+-----+ | ||
153 | | baz | | ||
154 | +-----+ | ||
155 | ``` | ||
156 | |||
157 | Alternatively, selecting `frobnicate` could present a user with an additional menu to choose between `bar` and `baz`. | ||
158 | |||
159 | ### Example | ||
160 | |||
161 | ```rust | ||
162 | fn main() { | ||
163 | let x: Entry/*cursor here*/ = todo!(); | ||
164 | } | ||
165 | ``` | ||
166 | |||
167 | Invoking code action at this position will yield two code actions for importing `Entry` from either `collections::HashMap` or `collection::BTreeMap`, grouped under a single "import" group. | ||
168 | |||
169 | ### Unresolved Questions | ||
170 | |||
171 | * Is a fixed two-level structure enough? | ||
172 | * Should we devise a general way to encode custom interaction protocols for GUI refactorings? | ||
diff --git a/editors/code/package.json b/editors/code/package.json index d899f60e3..78f647baa 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -313,22 +313,22 @@ | |||
313 | "rust-analyzer.inlayHints.enable": { | 313 | "rust-analyzer.inlayHints.enable": { |
314 | "type": "boolean", | 314 | "type": "boolean", |
315 | "default": true, | 315 | "default": true, |
316 | "description": "Disable all inlay hints" | 316 | "description": "Whether to show inlay hints" |
317 | }, | 317 | }, |
318 | "rust-analyzer.inlayHints.typeHints": { | 318 | "rust-analyzer.inlayHints.typeHints": { |
319 | "type": "boolean", | 319 | "type": "boolean", |
320 | "default": true, | 320 | "default": true, |
321 | "description": "Whether to show inlay type hints" | 321 | "description": "Whether to show inlay type hints for variables." |
322 | }, | 322 | }, |
323 | "rust-analyzer.inlayHints.chainingHints": { | 323 | "rust-analyzer.inlayHints.chainingHints": { |
324 | "type": "boolean", | 324 | "type": "boolean", |
325 | "default": true, | 325 | "default": true, |
326 | "description": "Whether to show inlay type hints for method chains" | 326 | "description": "Whether to show inlay type hints for method chains." |
327 | }, | 327 | }, |
328 | "rust-analyzer.inlayHints.parameterHints": { | 328 | "rust-analyzer.inlayHints.parameterHints": { |
329 | "type": "boolean", | 329 | "type": "boolean", |
330 | "default": true, | 330 | "default": true, |
331 | "description": "Whether to show function parameter name inlay hints at the call site" | 331 | "description": "Whether to show function parameter name inlay hints at the call site." |
332 | }, | 332 | }, |
333 | "rust-analyzer.inlayHints.maxLength": { | 333 | "rust-analyzer.inlayHints.maxLength": { |
334 | "type": [ | 334 | "type": [ |
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index fac1a0be3..d64f9a3f9 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts | |||
@@ -41,10 +41,12 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient | |||
41 | return client.sendRequest(lc.CodeActionRequest.type, params, token).then((values) => { | 41 | return client.sendRequest(lc.CodeActionRequest.type, params, token).then((values) => { |
42 | if (values === null) return undefined; | 42 | if (values === null) return undefined; |
43 | const result: (vscode.CodeAction | vscode.Command)[] = []; | 43 | const result: (vscode.CodeAction | vscode.Command)[] = []; |
44 | const groups = new Map<string, { index: number; items: vscode.CodeAction[] }>(); | ||
44 | for (const item of values) { | 45 | for (const item of values) { |
45 | if (lc.CodeAction.is(item)) { | 46 | if (lc.CodeAction.is(item)) { |
46 | const action = client.protocol2CodeConverter.asCodeAction(item); | 47 | const action = client.protocol2CodeConverter.asCodeAction(item); |
47 | if (isSnippetEdit(item)) { | 48 | const group = actionGroup(item); |
49 | if (isSnippetEdit(item) || group) { | ||
48 | action.command = { | 50 | action.command = { |
49 | command: "rust-analyzer.applySnippetWorkspaceEdit", | 51 | command: "rust-analyzer.applySnippetWorkspaceEdit", |
50 | title: "", | 52 | title: "", |
@@ -52,12 +54,38 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient | |||
52 | }; | 54 | }; |
53 | action.edit = undefined; | 55 | action.edit = undefined; |
54 | } | 56 | } |
55 | result.push(action); | 57 | |
58 | if (group) { | ||
59 | let entry = groups.get(group); | ||
60 | if (!entry) { | ||
61 | entry = { index: result.length, items: [] }; | ||
62 | groups.set(group, entry); | ||
63 | result.push(action); | ||
64 | } | ||
65 | entry.items.push(action); | ||
66 | } else { | ||
67 | result.push(action); | ||
68 | } | ||
56 | } else { | 69 | } else { |
57 | const command = client.protocol2CodeConverter.asCommand(item); | 70 | const command = client.protocol2CodeConverter.asCommand(item); |
58 | result.push(command); | 71 | result.push(command); |
59 | } | 72 | } |
60 | } | 73 | } |
74 | for (const [group, { index, items }] of groups) { | ||
75 | if (items.length === 1) { | ||
76 | result[index] = items[0]; | ||
77 | } else { | ||
78 | const action = new vscode.CodeAction(group); | ||
79 | action.command = { | ||
80 | command: "rust-analyzer.applyActionGroup", | ||
81 | title: "", | ||
82 | arguments: [items.map((item) => { | ||
83 | return { label: item.title, edit: item.command!!.arguments!![0] }; | ||
84 | })], | ||
85 | }; | ||
86 | result[index] = action; | ||
87 | } | ||
88 | } | ||
61 | return result; | 89 | return result; |
62 | }, | 90 | }, |
63 | (_error) => undefined | 91 | (_error) => undefined |
@@ -81,15 +109,16 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient | |||
81 | // implementations are still in the "proposed" category for 3.16. | 109 | // implementations are still in the "proposed" category for 3.16. |
82 | client.registerFeature(new CallHierarchyFeature(client)); | 110 | client.registerFeature(new CallHierarchyFeature(client)); |
83 | client.registerFeature(new SemanticTokensFeature(client)); | 111 | client.registerFeature(new SemanticTokensFeature(client)); |
84 | client.registerFeature(new SnippetTextEditFeature()); | 112 | client.registerFeature(new ExperimentalFeatures()); |
85 | 113 | ||
86 | return client; | 114 | return client; |
87 | } | 115 | } |
88 | 116 | ||
89 | class SnippetTextEditFeature implements lc.StaticFeature { | 117 | class ExperimentalFeatures implements lc.StaticFeature { |
90 | fillClientCapabilities(capabilities: lc.ClientCapabilities): void { | 118 | fillClientCapabilities(capabilities: lc.ClientCapabilities): void { |
91 | const caps: any = capabilities.experimental ?? {}; | 119 | const caps: any = capabilities.experimental ?? {}; |
92 | caps.snippetTextEdit = true; | 120 | caps.snippetTextEdit = true; |
121 | caps.codeActionGroup = true; | ||
93 | capabilities.experimental = caps; | 122 | capabilities.experimental = caps; |
94 | } | 123 | } |
95 | initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void { | 124 | initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void { |
@@ -107,3 +136,7 @@ function isSnippetEdit(action: lc.CodeAction): boolean { | |||
107 | } | 136 | } |
108 | return false; | 137 | return false; |
109 | } | 138 | } |
139 | |||
140 | function actionGroup(action: lc.CodeAction): string | undefined { | ||
141 | return (action as any).group; | ||
142 | } | ||
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts index 0937b495c..abb53a248 100644 --- a/editors/code/src/commands/index.ts +++ b/editors/code/src/commands/index.ts | |||
@@ -41,48 +41,65 @@ export function applySourceChange(ctx: Ctx): Cmd { | |||
41 | }; | 41 | }; |
42 | } | 42 | } |
43 | 43 | ||
44 | export function selectAndApplySourceChange(ctx: Ctx): Cmd { | 44 | export function applyActionGroup(_ctx: Ctx): Cmd { |
45 | return async (changes: ra.SourceChange[]) => { | 45 | return async (actions: { label: string; edit: vscode.WorkspaceEdit }[]) => { |
46 | if (changes.length === 1) { | 46 | const selectedAction = await vscode.window.showQuickPick(actions); |
47 | await sourceChange.applySourceChange(ctx, changes[0]); | 47 | if (!selectedAction) return; |
48 | } else if (changes.length > 0) { | 48 | await applySnippetWorkspaceEdit(selectedAction.edit); |
49 | const selectedChange = await vscode.window.showQuickPick(changes); | ||
50 | if (!selectedChange) return; | ||
51 | await sourceChange.applySourceChange(ctx, selectedChange); | ||
52 | } | ||
53 | }; | 49 | }; |
54 | } | 50 | } |
55 | 51 | ||
56 | export function applySnippetWorkspaceEdit(_ctx: Ctx): Cmd { | 52 | export function applySnippetWorkspaceEditCommand(_ctx: Ctx): Cmd { |
57 | return async (edit: vscode.WorkspaceEdit) => { | 53 | return async (edit: vscode.WorkspaceEdit) => { |
58 | assert(edit.entries().length === 1, `bad ws edit: ${JSON.stringify(edit)}`); | 54 | await applySnippetWorkspaceEdit(edit); |
59 | const [uri, edits] = edit.entries()[0]; | 55 | }; |
56 | } | ||
60 | 57 | ||
61 | const editor = vscode.window.visibleTextEditors.find((it) => it.document.uri.toString() === uri.toString()); | 58 | export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) { |
62 | if (!editor) return; | 59 | assert(edit.entries().length === 1, `bad ws edit: ${JSON.stringify(edit)}`); |
60 | const [uri, edits] = edit.entries()[0]; | ||
63 | 61 | ||
64 | let editWithSnippet: vscode.TextEdit | undefined = undefined; | 62 | const editor = vscode.window.visibleTextEditors.find((it) => it.document.uri.toString() === uri.toString()); |
65 | let lineDelta = 0; | 63 | if (!editor) return; |
66 | await editor.edit((builder) => { | 64 | |
67 | for (const indel of edits) { | 65 | let selection: vscode.Selection | undefined = undefined; |
68 | const isSnippet = indel.newText.indexOf('$0') !== -1 || indel.newText.indexOf('${') !== -1; | 66 | let lineDelta = 0; |
69 | if (isSnippet) { | 67 | await editor.edit((builder) => { |
70 | editWithSnippet = indel; | 68 | for (const indel of edits) { |
71 | } else { | 69 | const parsed = parseSnippet(indel.newText); |
72 | if (!editWithSnippet) { | 70 | if (parsed) { |
73 | lineDelta = (indel.newText.match(/\n/g) || []).length - (indel.range.end.line - indel.range.start.line); | 71 | const [newText, [placeholderStart, placeholderLength]] = parsed; |
74 | } | 72 | const prefix = newText.substr(0, placeholderStart); |
75 | builder.replace(indel.range, indel.newText); | 73 | const lastNewline = prefix.lastIndexOf('\n'); |
76 | } | 74 | |
75 | const startLine = indel.range.start.line + lineDelta + countLines(prefix); | ||
76 | const startColumn = lastNewline === -1 ? | ||
77 | indel.range.start.character + placeholderStart | ||
78 | : prefix.length - lastNewline - 1; | ||
79 | const endColumn = startColumn + placeholderLength; | ||
80 | selection = new vscode.Selection( | ||
81 | new vscode.Position(startLine, startColumn), | ||
82 | new vscode.Position(startLine, endColumn), | ||
83 | ); | ||
84 | builder.replace(indel.range, newText); | ||
85 | } else { | ||
86 | lineDelta = countLines(indel.newText) - (indel.range.end.line - indel.range.start.line); | ||
87 | builder.replace(indel.range, indel.newText); | ||
77 | } | 88 | } |
78 | }); | ||
79 | if (editWithSnippet) { | ||
80 | const snip = editWithSnippet as vscode.TextEdit; | ||
81 | const range = snip.range.with( | ||
82 | snip.range.start.with(snip.range.start.line + lineDelta), | ||
83 | snip.range.end.with(snip.range.end.line + lineDelta), | ||
84 | ); | ||
85 | await editor.insertSnippet(new vscode.SnippetString(snip.newText), range); | ||
86 | } | 89 | } |
87 | }; | 90 | }); |
91 | if (selection) editor.selection = selection; | ||
92 | } | ||
93 | |||
94 | function parseSnippet(snip: string): [string, [number, number]] | undefined { | ||
95 | const m = snip.match(/\$(0|\{0:([^}]*)\})/); | ||
96 | if (!m) return undefined; | ||
97 | const placeholder = m[2] ?? ""; | ||
98 | const range: [number, number] = [m.index!!, placeholder.length]; | ||
99 | const insert = snip.replace(m[0], placeholder); | ||
100 | return [insert, range]; | ||
101 | } | ||
102 | |||
103 | function countLines(text: string): number { | ||
104 | return (text.match(/\n/g) || []).length; | ||
88 | } | 105 | } |
diff --git a/editors/code/src/commands/join_lines.ts b/editors/code/src/commands/join_lines.ts index de0614653..0bf1ee6e6 100644 --- a/editors/code/src/commands/join_lines.ts +++ b/editors/code/src/commands/join_lines.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as ra from '../rust-analyzer-api'; | 1 | import * as ra from '../rust-analyzer-api'; |
2 | import * as lc from 'vscode-languageclient'; | ||
2 | 3 | ||
3 | import { Ctx, Cmd } from '../ctx'; | 4 | import { Ctx, Cmd } from '../ctx'; |
4 | import { applySourceChange } from '../source_change'; | ||
5 | 5 | ||
6 | export function joinLines(ctx: Ctx): Cmd { | 6 | export function joinLines(ctx: Ctx): Cmd { |
7 | return async () => { | 7 | return async () => { |
@@ -9,10 +9,14 @@ export function joinLines(ctx: Ctx): Cmd { | |||
9 | const client = ctx.client; | 9 | const client = ctx.client; |
10 | if (!editor || !client) return; | 10 | if (!editor || !client) return; |
11 | 11 | ||
12 | const change = await client.sendRequest(ra.joinLines, { | 12 | const items: lc.TextEdit[] = await client.sendRequest(ra.joinLines, { |
13 | range: client.code2ProtocolConverter.asRange(editor.selection), | 13 | ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)), |
14 | textDocument: { uri: editor.document.uri.toString() }, | 14 | textDocument: { uri: editor.document.uri.toString() }, |
15 | }); | 15 | }); |
16 | await applySourceChange(ctx, change); | 16 | editor.edit((builder) => { |
17 | client.protocol2CodeConverter.asTextEdits(items).forEach((edit) => { | ||
18 | builder.replace(edit.range, edit.newText); | ||
19 | }); | ||
20 | }); | ||
17 | }; | 21 | }; |
18 | } | 22 | } |
diff --git a/editors/code/src/commands/on_enter.ts b/editors/code/src/commands/on_enter.ts index 285849db7..a7871c31e 100644 --- a/editors/code/src/commands/on_enter.ts +++ b/editors/code/src/commands/on_enter.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as ra from '../rust-analyzer-api'; | 2 | import * as ra from '../rust-analyzer-api'; |
3 | 3 | ||
4 | import { applySourceChange } from '../source_change'; | ||
5 | import { Cmd, Ctx } from '../ctx'; | 4 | import { Cmd, Ctx } from '../ctx'; |
5 | import { applySnippetWorkspaceEdit } from '.'; | ||
6 | 6 | ||
7 | async function handleKeypress(ctx: Ctx) { | 7 | async function handleKeypress(ctx: Ctx) { |
8 | const editor = ctx.activeRustEditor; | 8 | const editor = ctx.activeRustEditor; |
@@ -21,7 +21,8 @@ async function handleKeypress(ctx: Ctx) { | |||
21 | }); | 21 | }); |
22 | if (!change) return false; | 22 | if (!change) return false; |
23 | 23 | ||
24 | await applySourceChange(ctx, change); | 24 | const workspaceEdit = client.protocol2CodeConverter.asWorkspaceEdit(change); |
25 | await applySnippetWorkspaceEdit(workspaceEdit); | ||
25 | return true; | 26 | return true; |
26 | } | 27 | } |
27 | 28 | ||
diff --git a/editors/code/src/commands/ssr.ts b/editors/code/src/commands/ssr.ts index 4ef8cdf04..5d40a64d2 100644 --- a/editors/code/src/commands/ssr.ts +++ b/editors/code/src/commands/ssr.ts | |||
@@ -2,7 +2,6 @@ import * as vscode from 'vscode'; | |||
2 | import * as ra from "../rust-analyzer-api"; | 2 | import * as ra from "../rust-analyzer-api"; |
3 | 3 | ||
4 | import { Ctx, Cmd } from '../ctx'; | 4 | import { Ctx, Cmd } from '../ctx'; |
5 | import { applySourceChange } from '../source_change'; | ||
6 | 5 | ||
7 | export function ssr(ctx: Ctx): Cmd { | 6 | export function ssr(ctx: Ctx): Cmd { |
8 | return async () => { | 7 | return async () => { |
@@ -22,11 +21,10 @@ export function ssr(ctx: Ctx): Cmd { | |||
22 | } | 21 | } |
23 | }; | 22 | }; |
24 | const request = await vscode.window.showInputBox(options); | 23 | const request = await vscode.window.showInputBox(options); |
25 | |||
26 | if (!request) return; | 24 | if (!request) return; |
27 | 25 | ||
28 | const change = await client.sendRequest(ra.ssr, { query: request, parseOnly: false }); | 26 | const edit = await client.sendRequest(ra.ssr, { query: request, parseOnly: false }); |
29 | 27 | ||
30 | await applySourceChange(ctx, change); | 28 | await vscode.workspace.applyEdit(client.protocol2CodeConverter.asWorkspaceEdit(edit)); |
31 | }; | 29 | }; |
32 | } | 30 | } |
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index ad3c1a192..3405634f3 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -92,8 +92,8 @@ export async function activate(context: vscode.ExtensionContext) { | |||
92 | ctx.registerCommand('debugSingle', commands.debugSingle); | 92 | ctx.registerCommand('debugSingle', commands.debugSingle); |
93 | ctx.registerCommand('showReferences', commands.showReferences); | 93 | ctx.registerCommand('showReferences', commands.showReferences); |
94 | ctx.registerCommand('applySourceChange', commands.applySourceChange); | 94 | ctx.registerCommand('applySourceChange', commands.applySourceChange); |
95 | ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEdit); | 95 | ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEditCommand); |
96 | ctx.registerCommand('selectAndApplySourceChange', commands.selectAndApplySourceChange); | 96 | ctx.registerCommand('applyActionGroup', commands.applyActionGroup); |
97 | 97 | ||
98 | ctx.pushCleanup(activateTaskProvider(workspaceFolder)); | 98 | ctx.pushCleanup(activateTaskProvider(workspaceFolder)); |
99 | 99 | ||
diff --git a/editors/code/src/rust-analyzer-api.ts b/editors/code/src/rust-analyzer-api.ts index 400ac3714..73f36432f 100644 --- a/editors/code/src/rust-analyzer-api.ts +++ b/editors/code/src/rust-analyzer-api.ts | |||
@@ -64,12 +64,12 @@ export const parentModule = request<lc.TextDocumentPositionParams, Vec<lc.Locati | |||
64 | 64 | ||
65 | export interface JoinLinesParams { | 65 | export interface JoinLinesParams { |
66 | textDocument: lc.TextDocumentIdentifier; | 66 | textDocument: lc.TextDocumentIdentifier; |
67 | range: lc.Range; | 67 | ranges: lc.Range[]; |
68 | } | 68 | } |
69 | export const joinLines = request<JoinLinesParams, SourceChange>("joinLines"); | 69 | export const joinLines = new lc.RequestType<JoinLinesParams, lc.TextEdit[], unknown>('experimental/joinLines'); |
70 | 70 | ||
71 | 71 | ||
72 | export const onEnter = request<lc.TextDocumentPositionParams, Option<SourceChange>>("onEnter"); | 72 | export const onEnter = request<lc.TextDocumentPositionParams, Option<lc.WorkspaceEdit>>("onEnter"); |
73 | 73 | ||
74 | export interface RunnablesParams { | 74 | export interface RunnablesParams { |
75 | textDocument: lc.TextDocumentIdentifier; | 75 | textDocument: lc.TextDocumentIdentifier; |
@@ -112,7 +112,7 @@ export interface SsrParams { | |||
112 | query: string; | 112 | query: string; |
113 | parseOnly: boolean; | 113 | parseOnly: boolean; |
114 | } | 114 | } |
115 | export const ssr = request<SsrParams, SourceChange>("ssr"); | 115 | export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, unknown>('experimental/ssr'); |
116 | 116 | ||
117 | 117 | ||
118 | export const publishDecorations = notification<PublishDecorationsParams>("publishDecorations"); | 118 | export const publishDecorations = notification<PublishDecorationsParams>("publishDecorations"); |