mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 13:03:31 +00:00
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>
This commit is contained in:
commit
c6db6e2352
5 changed files with 150 additions and 40 deletions
|
@ -1,6 +1,9 @@
|
||||||
//! FIXME: write short doc here
|
//! FIXME: write short doc here
|
||||||
|
|
||||||
use ra_syntax::{ast::AstNode, TextRange, TextUnit};
|
use ra_syntax::{
|
||||||
|
ast::{self, AstNode},
|
||||||
|
TextRange, TextUnit,
|
||||||
|
};
|
||||||
use ra_text_edit::TextEdit;
|
use ra_text_edit::TextEdit;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -21,13 +24,8 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
let receiver_text = if ctx.dot_receiver_is_ambiguous_float_literal {
|
let receiver_text =
|
||||||
let text = dot_receiver.syntax().text();
|
get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
|
||||||
let without_dot = ..text.len() - TextUnit::of_char('.');
|
|
||||||
text.slice(without_dot).to_string()
|
|
||||||
} else {
|
|
||||||
dot_receiver.syntax().text().to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) {
|
let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) {
|
||||||
Some(it) => it,
|
Some(it) => it,
|
||||||
|
@ -35,10 +33,17 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
|
||||||
};
|
};
|
||||||
|
|
||||||
if receiver_ty.is_bool() || receiver_ty.is_unknown() {
|
if receiver_ty.is_bool() || receiver_ty.is_unknown() {
|
||||||
postfix_snippet(ctx, "if", "if expr {}", &format!("if {} {{$0}}", receiver_text))
|
|
||||||
.add_to(acc);
|
|
||||||
postfix_snippet(
|
postfix_snippet(
|
||||||
ctx,
|
ctx,
|
||||||
|
&dot_receiver,
|
||||||
|
"if",
|
||||||
|
"if expr {}",
|
||||||
|
&format!("if {} {{$0}}", receiver_text),
|
||||||
|
)
|
||||||
|
.add_to(acc);
|
||||||
|
postfix_snippet(
|
||||||
|
ctx,
|
||||||
|
&dot_receiver,
|
||||||
"while",
|
"while",
|
||||||
"while expr {}",
|
"while expr {}",
|
||||||
&format!("while {} {{\n$0\n}}", receiver_text),
|
&format!("while {} {{\n$0\n}}", receiver_text),
|
||||||
|
@ -46,28 +51,70 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
|
||||||
.add_to(acc);
|
.add_to(acc);
|
||||||
}
|
}
|
||||||
|
|
||||||
postfix_snippet(ctx, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc);
|
// !&&&42 is a compiler error, ergo process it before considering the references
|
||||||
|
postfix_snippet(ctx, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc);
|
||||||
|
|
||||||
postfix_snippet(ctx, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc);
|
postfix_snippet(ctx, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc);
|
||||||
postfix_snippet(ctx, "refm", "&mut expr", &format!("&mut {}", receiver_text)).add_to(acc);
|
postfix_snippet(ctx, &dot_receiver, "refm", "&mut expr", &format!("&mut {}", receiver_text))
|
||||||
|
.add_to(acc);
|
||||||
|
|
||||||
|
// The rest of the postfix completions create an expression that moves an argument,
|
||||||
|
// so it's better to consider references now to avoid breaking the compilation
|
||||||
|
let dot_receiver = include_references(dot_receiver);
|
||||||
|
let receiver_text =
|
||||||
|
get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
|
||||||
|
|
||||||
postfix_snippet(
|
postfix_snippet(
|
||||||
ctx,
|
ctx,
|
||||||
|
&dot_receiver,
|
||||||
"match",
|
"match",
|
||||||
"match expr {}",
|
"match expr {}",
|
||||||
&format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text),
|
&format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text),
|
||||||
)
|
)
|
||||||
.add_to(acc);
|
.add_to(acc);
|
||||||
|
|
||||||
postfix_snippet(ctx, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc);
|
postfix_snippet(
|
||||||
|
ctx,
|
||||||
|
&dot_receiver,
|
||||||
|
"box",
|
||||||
|
"Box::new(expr)",
|
||||||
|
&format!("Box::new({})", receiver_text),
|
||||||
|
)
|
||||||
|
.add_to(acc);
|
||||||
|
|
||||||
postfix_snippet(ctx, "box", "Box::new(expr)", &format!("Box::new({})", receiver_text))
|
postfix_snippet(ctx, &dot_receiver, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text))
|
||||||
.add_to(acc);
|
.add_to(acc);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder {
|
fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
|
||||||
|
if receiver_is_ambiguous_float_literal {
|
||||||
|
let text = receiver.syntax().text();
|
||||||
|
let without_dot = ..text.len() - TextUnit::of_char('.');
|
||||||
|
text.slice(without_dot).to_string()
|
||||||
|
} else {
|
||||||
|
receiver.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn include_references(initial_element: &ast::Expr) -> ast::Expr {
|
||||||
|
let mut resulting_element = initial_element.clone();
|
||||||
|
while let Some(parent_ref_element) =
|
||||||
|
resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
|
||||||
|
{
|
||||||
|
resulting_element = ast::Expr::from(parent_ref_element);
|
||||||
|
}
|
||||||
|
resulting_element
|
||||||
|
}
|
||||||
|
|
||||||
|
fn postfix_snippet(
|
||||||
|
ctx: &CompletionContext,
|
||||||
|
receiver: &ast::Expr,
|
||||||
|
label: &str,
|
||||||
|
detail: &str,
|
||||||
|
snippet: &str,
|
||||||
|
) -> Builder {
|
||||||
let edit = {
|
let edit = {
|
||||||
let receiver_syntax = ctx.dot_receiver.as_ref().expect("no receiver available").syntax();
|
let receiver_syntax = receiver.syntax();
|
||||||
let receiver_range = ctx.sema.original_range(receiver_syntax).range;
|
let receiver_range = ctx.sema.original_range(receiver_syntax).range;
|
||||||
let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end());
|
let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end());
|
||||||
TextEdit::replace(delete_range, snippet.to_string())
|
TextEdit::replace(delete_range, snippet.to_string())
|
||||||
|
@ -340,4 +387,63 @@ mod tests {
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn postfix_completion_for_references() {
|
||||||
|
assert_debug_snapshot!(
|
||||||
|
do_postfix_completion(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
&&&&42.<|>
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
@r###"
|
||||||
|
[
|
||||||
|
CompletionItem {
|
||||||
|
label: "box",
|
||||||
|
source_range: [56; 56),
|
||||||
|
delete: [49; 56),
|
||||||
|
insert: "Box::new(&&&&42)",
|
||||||
|
detail: "Box::new(expr)",
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "dbg",
|
||||||
|
source_range: [56; 56),
|
||||||
|
delete: [49; 56),
|
||||||
|
insert: "dbg!(&&&&42)",
|
||||||
|
detail: "dbg!(expr)",
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "match",
|
||||||
|
source_range: [56; 56),
|
||||||
|
delete: [49; 56),
|
||||||
|
insert: "match &&&&42 {\n ${1:_} => {$0\\},\n}",
|
||||||
|
detail: "match expr {}",
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "not",
|
||||||
|
source_range: [56; 56),
|
||||||
|
delete: [53; 56),
|
||||||
|
insert: "!42",
|
||||||
|
detail: "!expr",
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "ref",
|
||||||
|
source_range: [56; 56),
|
||||||
|
delete: [53; 56),
|
||||||
|
insert: "&42",
|
||||||
|
detail: "&expr",
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "refm",
|
||||||
|
source_range: [56; 56),
|
||||||
|
delete: [53; 56),
|
||||||
|
insert: "&mut 42",
|
||||||
|
detail: "&mut expr",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,10 +73,18 @@
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"args": {
|
"args": {
|
||||||
"type": "array"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"type": "object"
|
"type": "object",
|
||||||
|
"patternProperties": {
|
||||||
|
".+": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,8 +99,10 @@ export async function createClient(config: Config, serverPath: string): Promise<
|
||||||
// Note that while the CallHierarchyFeature is stable the LSP protocol is not.
|
// Note that while the CallHierarchyFeature is stable the LSP protocol is not.
|
||||||
res.registerFeature(new CallHierarchyFeature(res));
|
res.registerFeature(new CallHierarchyFeature(res));
|
||||||
|
|
||||||
if (config.highlightingSemanticTokens) {
|
if (config.package.enableProposedApi) {
|
||||||
res.registerFeature(new SemanticTokensFeature(res));
|
if (config.highlightingSemanticTokens) {
|
||||||
|
res.registerFeature(new SemanticTokensFeature(res));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|
|
@ -38,17 +38,11 @@ export class Config {
|
||||||
]
|
]
|
||||||
.map(opt => `${this.rootSection}.${opt}`);
|
.map(opt => `${this.rootSection}.${opt}`);
|
||||||
|
|
||||||
readonly packageJsonVersion: string = vscode
|
readonly package: {
|
||||||
.extensions
|
version: string;
|
||||||
.getExtension(this.extensionId)!
|
releaseTag: string | undefined;
|
||||||
.packageJSON
|
enableProposedApi: boolean | undefined;
|
||||||
.version;
|
} = vscode.extensions.getExtension(this.extensionId)!.packageJSON;
|
||||||
|
|
||||||
readonly releaseTag: string | undefined = vscode
|
|
||||||
.extensions
|
|
||||||
.getExtension(this.extensionId)!
|
|
||||||
.packageJSON
|
|
||||||
.releaseTag ?? undefined;
|
|
||||||
|
|
||||||
private cfg!: vscode.WorkspaceConfiguration;
|
private cfg!: vscode.WorkspaceConfiguration;
|
||||||
|
|
||||||
|
@ -62,7 +56,7 @@ export class Config {
|
||||||
const enableLogging = this.cfg.get("trace.extension") as boolean;
|
const enableLogging = this.cfg.get("trace.extension") as boolean;
|
||||||
log.setEnabled(enableLogging);
|
log.setEnabled(enableLogging);
|
||||||
log.debug(
|
log.debug(
|
||||||
"Extension version:", this.packageJsonVersion,
|
"Extension version:", this.package.version,
|
||||||
"using configuration:", this.cfg
|
"using configuration:", this.cfg
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,9 +110,9 @@ async function bootstrap(config: Config, state: PersistentState): Promise<string
|
||||||
}
|
}
|
||||||
|
|
||||||
async function bootstrapExtension(config: Config, state: PersistentState): Promise<void> {
|
async function bootstrapExtension(config: Config, state: PersistentState): Promise<void> {
|
||||||
if (config.releaseTag === undefined) return;
|
if (config.package.releaseTag === undefined) return;
|
||||||
if (config.channel === "stable") {
|
if (config.channel === "stable") {
|
||||||
if (config.releaseTag === NIGHTLY_TAG) {
|
if (config.package.releaseTag === NIGHTLY_TAG) {
|
||||||
vscode.window.showWarningMessage(`You are running a nightly version of rust-analyzer extension.
|
vscode.window.showWarningMessage(`You are running a nightly version of rust-analyzer extension.
|
||||||
To switch to stable, uninstall the extension and re-install it from the marketplace`);
|
To switch to stable, uninstall the extension and re-install it from the marketplace`);
|
||||||
}
|
}
|
||||||
|
@ -185,7 +185,7 @@ async function getServer(config: Config, state: PersistentState): Promise<string
|
||||||
}
|
}
|
||||||
return explicitPath;
|
return explicitPath;
|
||||||
};
|
};
|
||||||
if (config.releaseTag === undefined) return "rust-analyzer";
|
if (config.package.releaseTag === undefined) return "rust-analyzer";
|
||||||
|
|
||||||
let binaryName: string | undefined = undefined;
|
let binaryName: string | undefined = undefined;
|
||||||
if (process.arch === "x64" || process.arch === "x32") {
|
if (process.arch === "x64" || process.arch === "x32") {
|
||||||
|
@ -211,21 +211,21 @@ async function getServer(config: Config, state: PersistentState): Promise<string
|
||||||
await state.updateServerVersion(undefined);
|
await state.updateServerVersion(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.serverVersion === config.packageJsonVersion) return dest;
|
if (state.serverVersion === config.package.version) return dest;
|
||||||
|
|
||||||
if (config.askBeforeDownload) {
|
if (config.askBeforeDownload) {
|
||||||
const userResponse = await vscode.window.showInformationMessage(
|
const userResponse = await vscode.window.showInformationMessage(
|
||||||
`Language server version ${config.packageJsonVersion} for rust-analyzer is not installed.`,
|
`Language server version ${config.package.version} for rust-analyzer is not installed.`,
|
||||||
"Download now"
|
"Download now"
|
||||||
);
|
);
|
||||||
if (userResponse !== "Download now") return dest;
|
if (userResponse !== "Download now") return dest;
|
||||||
}
|
}
|
||||||
|
|
||||||
const release = await fetchRelease(config.releaseTag);
|
const release = await fetchRelease(config.package.releaseTag);
|
||||||
const artifact = release.assets.find(artifact => artifact.name === binaryName);
|
const artifact = release.assets.find(artifact => artifact.name === binaryName);
|
||||||
assert(!!artifact, `Bad release: ${JSON.stringify(release)}`);
|
assert(!!artifact, `Bad release: ${JSON.stringify(release)}`);
|
||||||
|
|
||||||
await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 });
|
await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 });
|
||||||
await state.updateServerVersion(config.packageJsonVersion);
|
await state.updateServerVersion(config.package.version);
|
||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue