Skip to content

Commit c6db6e2

Browse files
bors[bot]VeetahaSomeoneToIgnorematklad
authored
3696: vscode: more type safety r=matklad a=Veetaha 3698: Consider references when applying postfix completions r=matklad a=SomeoneToIgnore Sometimes my RA debugging workflow breaks because `.dbg` is applied to the variable that is used later in the code. It's safer to consider the refences to avoid this for completions that may trigger the move. 3703: Don't try to enable proposed API's on stable r=matklad a=matklad bors r+ 🤖 Co-authored-by: veetaha <veetaha2@gmail.com> Co-authored-by: Kirill Bulatov <mail4score@gmail.com> Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
4 parents d5f77f3 + 4cee3e9 + 4e43df2 + be4977d commit c6db6e2

File tree

5 files changed

+150
-40
lines changed

5 files changed

+150
-40
lines changed

crates/ra_ide/src/completion/complete_postfix.rs

Lines changed: 123 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
//! FIXME: write short doc here
22
3-
use ra_syntax::{ast::AstNode, TextRange, TextUnit};
3+
use ra_syntax::{
4+
ast::{self, AstNode},
5+
TextRange, TextUnit,
6+
};
47
use ra_text_edit::TextEdit;
58

69
use crate::{
@@ -21,53 +24,97 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
2124
None => return,
2225
};
2326

24-
let receiver_text = if ctx.dot_receiver_is_ambiguous_float_literal {
25-
let text = dot_receiver.syntax().text();
26-
let without_dot = ..text.len() - TextUnit::of_char('.');
27-
text.slice(without_dot).to_string()
28-
} else {
29-
dot_receiver.syntax().text().to_string()
30-
};
27+
let receiver_text =
28+
get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
3129

3230
let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) {
3331
Some(it) => it,
3432
None => return,
3533
};
3634

3735
if receiver_ty.is_bool() || receiver_ty.is_unknown() {
38-
postfix_snippet(ctx, "if", "if expr {}", &format!("if {} {{$0}}", receiver_text))
39-
.add_to(acc);
4036
postfix_snippet(
4137
ctx,
38+
&dot_receiver,
39+
"if",
40+
"if expr {}",
41+
&format!("if {} {{$0}}", receiver_text),
42+
)
43+
.add_to(acc);
44+
postfix_snippet(
45+
ctx,
46+
&dot_receiver,
4247
"while",
4348
"while expr {}",
4449
&format!("while {} {{\n$0\n}}", receiver_text),
4550
)
4651
.add_to(acc);
4752
}
4853

49-
postfix_snippet(ctx, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc);
54+
// !&&&42 is a compiler error, ergo process it before considering the references
55+
postfix_snippet(ctx, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc);
5056

51-
postfix_snippet(ctx, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc);
52-
postfix_snippet(ctx, "refm", "&mut expr", &format!("&mut {}", receiver_text)).add_to(acc);
57+
postfix_snippet(ctx, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc);
58+
postfix_snippet(ctx, &dot_receiver, "refm", "&mut expr", &format!("&mut {}", receiver_text))
59+
.add_to(acc);
60+
61+
// The rest of the postfix completions create an expression that moves an argument,
62+
// so it's better to consider references now to avoid breaking the compilation
63+
let dot_receiver = include_references(dot_receiver);
64+
let receiver_text =
65+
get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
5366

5467
postfix_snippet(
5568
ctx,
69+
&dot_receiver,
5670
"match",
5771
"match expr {}",
5872
&format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text),
5973
)
6074
.add_to(acc);
6175

62-
postfix_snippet(ctx, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc);
76+
postfix_snippet(
77+
ctx,
78+
&dot_receiver,
79+
"box",
80+
"Box::new(expr)",
81+
&format!("Box::new({})", receiver_text),
82+
)
83+
.add_to(acc);
6384

64-
postfix_snippet(ctx, "box", "Box::new(expr)", &format!("Box::new({})", receiver_text))
85+
postfix_snippet(ctx, &dot_receiver, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text))
6586
.add_to(acc);
6687
}
6788

68-
fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder {
89+
fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
90+
if receiver_is_ambiguous_float_literal {
91+
let text = receiver.syntax().text();
92+
let without_dot = ..text.len() - TextUnit::of_char('.');
93+
text.slice(without_dot).to_string()
94+
} else {
95+
receiver.to_string()
96+
}
97+
}
98+
99+
fn include_references(initial_element: &ast::Expr) -> ast::Expr {
100+
let mut resulting_element = initial_element.clone();
101+
while let Some(parent_ref_element) =
102+
resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
103+
{
104+
resulting_element = ast::Expr::from(parent_ref_element);
105+
}
106+
resulting_element
107+
}
108+
109+
fn postfix_snippet(
110+
ctx: &CompletionContext,
111+
receiver: &ast::Expr,
112+
label: &str,
113+
detail: &str,
114+
snippet: &str,
115+
) -> Builder {
69116
let edit = {
70-
let receiver_syntax = ctx.dot_receiver.as_ref().expect("no receiver available").syntax();
117+
let receiver_syntax = receiver.syntax();
71118
let receiver_range = ctx.sema.original_range(receiver_syntax).range;
72119
let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end());
73120
TextEdit::replace(delete_range, snippet.to_string())
@@ -340,4 +387,63 @@ mod tests {
340387
"###
341388
);
342389
}
390+
391+
#[test]
392+
fn postfix_completion_for_references() {
393+
assert_debug_snapshot!(
394+
do_postfix_completion(
395+
r#"
396+
fn main() {
397+
&&&&42.<|>
398+
}
399+
"#,
400+
),
401+
@r###"
402+
[
403+
CompletionItem {
404+
label: "box",
405+
source_range: [56; 56),
406+
delete: [49; 56),
407+
insert: "Box::new(&&&&42)",
408+
detail: "Box::new(expr)",
409+
},
410+
CompletionItem {
411+
label: "dbg",
412+
source_range: [56; 56),
413+
delete: [49; 56),
414+
insert: "dbg!(&&&&42)",
415+
detail: "dbg!(expr)",
416+
},
417+
CompletionItem {
418+
label: "match",
419+
source_range: [56; 56),
420+
delete: [49; 56),
421+
insert: "match &&&&42 {\n ${1:_} => {$0\\},\n}",
422+
detail: "match expr {}",
423+
},
424+
CompletionItem {
425+
label: "not",
426+
source_range: [56; 56),
427+
delete: [53; 56),
428+
insert: "!42",
429+
detail: "!expr",
430+
},
431+
CompletionItem {
432+
label: "ref",
433+
source_range: [56; 56),
434+
delete: [53; 56),
435+
insert: "&42",
436+
detail: "&expr",
437+
},
438+
CompletionItem {
439+
label: "refm",
440+
source_range: [56; 56),
441+
delete: [53; 56),
442+
insert: "&mut 42",
443+
detail: "&mut expr",
444+
},
445+
]
446+
"###
447+
);
448+
}
343449
}

editors/code/package.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,18 @@
7373
"type": "string"
7474
},
7575
"args": {
76-
"type": "array"
76+
"type": "array",
77+
"items": {
78+
"type": "string"
79+
}
7780
},
7881
"env": {
79-
"type": "object"
82+
"type": "object",
83+
"patternProperties": {
84+
".+": {
85+
"type": "string"
86+
}
87+
}
8088
}
8189
}
8290
}

editors/code/src/client.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,10 @@ export async function createClient(config: Config, serverPath: string): Promise<
9999
// Note that while the CallHierarchyFeature is stable the LSP protocol is not.
100100
res.registerFeature(new CallHierarchyFeature(res));
101101

102-
if (config.highlightingSemanticTokens) {
103-
res.registerFeature(new SemanticTokensFeature(res));
102+
if (config.package.enableProposedApi) {
103+
if (config.highlightingSemanticTokens) {
104+
res.registerFeature(new SemanticTokensFeature(res));
105+
}
104106
}
105107

106108
return res;

editors/code/src/config.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,11 @@ export class Config {
3838
]
3939
.map(opt => `${this.rootSection}.${opt}`);
4040

41-
readonly packageJsonVersion: string = vscode
42-
.extensions
43-
.getExtension(this.extensionId)!
44-
.packageJSON
45-
.version;
46-
47-
readonly releaseTag: string | undefined = vscode
48-
.extensions
49-
.getExtension(this.extensionId)!
50-
.packageJSON
51-
.releaseTag ?? undefined;
41+
readonly package: {
42+
version: string;
43+
releaseTag: string | undefined;
44+
enableProposedApi: boolean | undefined;
45+
} = vscode.extensions.getExtension(this.extensionId)!.packageJSON;
5246

5347
private cfg!: vscode.WorkspaceConfiguration;
5448

@@ -62,7 +56,7 @@ export class Config {
6256
const enableLogging = this.cfg.get("trace.extension") as boolean;
6357
log.setEnabled(enableLogging);
6458
log.debug(
65-
"Extension version:", this.packageJsonVersion,
59+
"Extension version:", this.package.version,
6660
"using configuration:", this.cfg
6761
);
6862
}

editors/code/src/main.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,9 @@ async function bootstrap(config: Config, state: PersistentState): Promise<string
110110
}
111111

112112
async function bootstrapExtension(config: Config, state: PersistentState): Promise<void> {
113-
if (config.releaseTag === undefined) return;
113+
if (config.package.releaseTag === undefined) return;
114114
if (config.channel === "stable") {
115-
if (config.releaseTag === NIGHTLY_TAG) {
115+
if (config.package.releaseTag === NIGHTLY_TAG) {
116116
vscode.window.showWarningMessage(`You are running a nightly version of rust-analyzer extension.
117117
To switch to stable, uninstall the extension and re-install it from the marketplace`);
118118
}
@@ -185,7 +185,7 @@ async function getServer(config: Config, state: PersistentState): Promise<string
185185
}
186186
return explicitPath;
187187
};
188-
if (config.releaseTag === undefined) return "rust-analyzer";
188+
if (config.package.releaseTag === undefined) return "rust-analyzer";
189189

190190
let binaryName: string | undefined = undefined;
191191
if (process.arch === "x64" || process.arch === "x32") {
@@ -211,21 +211,21 @@ async function getServer(config: Config, state: PersistentState): Promise<string
211211
await state.updateServerVersion(undefined);
212212
}
213213

214-
if (state.serverVersion === config.packageJsonVersion) return dest;
214+
if (state.serverVersion === config.package.version) return dest;
215215

216216
if (config.askBeforeDownload) {
217217
const userResponse = await vscode.window.showInformationMessage(
218-
`Language server version ${config.packageJsonVersion} for rust-analyzer is not installed.`,
218+
`Language server version ${config.package.version} for rust-analyzer is not installed.`,
219219
"Download now"
220220
);
221221
if (userResponse !== "Download now") return dest;
222222
}
223223

224-
const release = await fetchRelease(config.releaseTag);
224+
const release = await fetchRelease(config.package.releaseTag);
225225
const artifact = release.assets.find(artifact => artifact.name === binaryName);
226226
assert(!!artifact, `Bad release: ${JSON.stringify(release)}`);
227227

228228
await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 });
229-
await state.updateServerVersion(config.packageJsonVersion);
229+
await state.updateServerVersion(config.package.version);
230230
return dest;
231231
}

0 commit comments

Comments
 (0)