mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-13 05:38:46 +00:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
58e15d12e4
47 changed files with 1414 additions and 1262 deletions
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
|
@ -190,4 +190,4 @@ jobs:
|
|||
- name: Publish Extension
|
||||
working-directory: ./editors/code
|
||||
# token from https://dev.azure.com/rust-analyzer/
|
||||
run: ./node_modules/vsce/out/vsce publish 0.1.$(date +%Y%m%d) --pat ${{ secrets.MARKETPLACE_TOKEN }}
|
||||
run: npx vsce publish 0.1.$(date +%Y%m%d) --pat ${{ secrets.MARKETPLACE_TOKEN }}
|
||||
|
|
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -1015,6 +1015,7 @@ name = "ra_cli"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"itertools",
|
||||
"pico-args",
|
||||
"ra_batch",
|
||||
"ra_db",
|
||||
|
@ -1024,6 +1025,7 @@ dependencies = [
|
|||
"ra_ide",
|
||||
"ra_prof",
|
||||
"ra_syntax",
|
||||
"rand 0.7.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1174,7 +1176,6 @@ dependencies = [
|
|||
"ra_prof",
|
||||
"ra_syntax",
|
||||
"ra_text_edit",
|
||||
"rand 0.7.3",
|
||||
"rayon",
|
||||
"rustc-hash",
|
||||
"superslice",
|
||||
|
|
|
@ -31,3 +31,8 @@ opt-level = 0
|
|||
|
||||
[patch.'crates-io']
|
||||
# rowan = { path = "../rowan" }
|
||||
|
||||
[patch.'https://github.com/rust-lang/chalk.git']
|
||||
# chalk-solve = { path = "../chalk/chalk-solve" }
|
||||
# chalk-rust-ir = { path = "../chalk/chalk-rust-ir" }
|
||||
# chalk-ir = { path = "../chalk/chalk-ir" }
|
||||
|
|
|
@ -6,8 +6,10 @@ authors = ["rust-analyzer developers"]
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.8.0"
|
||||
pico-args = "0.3.0"
|
||||
env_logger = { version = "0.7.1", default-features = false }
|
||||
rand = { version = "0.7.0", features = ["small_rng"] }
|
||||
|
||||
ra_syntax = { path = "../ra_syntax" }
|
||||
ra_ide = { path = "../ra_ide" }
|
||||
|
|
|
@ -20,6 +20,8 @@ pub(crate) enum Op {
|
|||
}
|
||||
|
||||
pub(crate) fn run(verbose: bool, path: &Path, op: Op) -> Result<()> {
|
||||
ra_prof::init();
|
||||
|
||||
let start = Instant::now();
|
||||
eprint!("loading: ");
|
||||
let (mut host, roots) = ra_batch::load_cargo(path)?;
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
use std::{collections::HashSet, fmt::Write, path::Path, time::Instant};
|
||||
|
||||
use itertools::Itertools;
|
||||
use rand::{seq::SliceRandom, thread_rng};
|
||||
|
||||
use hir::{
|
||||
db::{DefDatabase, HirDatabase},
|
||||
AssocItem, Crate, HasSource, HirDisplay, ModuleDef,
|
||||
|
@ -19,6 +22,7 @@ pub fn run(
|
|||
path: &Path,
|
||||
only: Option<&str>,
|
||||
with_deps: bool,
|
||||
randomize: bool,
|
||||
) -> Result<()> {
|
||||
let db_load_time = Instant::now();
|
||||
let (mut host, roots) = ra_batch::load_cargo(path)?;
|
||||
|
@ -41,7 +45,11 @@ pub fn run(
|
|||
})
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
for krate in Crate::all(db) {
|
||||
let mut krates = Crate::all(db);
|
||||
if randomize {
|
||||
krates.shuffle(&mut thread_rng());
|
||||
}
|
||||
for krate in krates {
|
||||
let module = krate.root_module(db).expect("crate without root module");
|
||||
let file_id = module.definition_source(db).file_id;
|
||||
if members.contains(&db.file_source_root(file_id.original_file(db))) {
|
||||
|
@ -50,6 +58,10 @@ pub fn run(
|
|||
}
|
||||
}
|
||||
|
||||
if randomize {
|
||||
visit_queue.shuffle(&mut thread_rng());
|
||||
}
|
||||
|
||||
println!("Crates in this dir: {}", num_crates);
|
||||
let mut num_decls = 0;
|
||||
let mut funcs = Vec::new();
|
||||
|
@ -79,10 +91,14 @@ pub fn run(
|
|||
println!("Total functions: {}", funcs.len());
|
||||
println!("Item Collection: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage());
|
||||
|
||||
if randomize {
|
||||
funcs.shuffle(&mut thread_rng());
|
||||
}
|
||||
|
||||
let inference_time = Instant::now();
|
||||
let mut bar = match verbosity {
|
||||
Verbosity::Verbose | Verbosity::Normal => ProgressReport::new(funcs.len() as u64),
|
||||
Verbosity::Quiet => ProgressReport::hidden(),
|
||||
Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
|
||||
_ => ProgressReport::new(funcs.len() as u64),
|
||||
};
|
||||
|
||||
bar.tick();
|
||||
|
@ -92,7 +108,20 @@ pub fn run(
|
|||
let mut num_type_mismatches = 0;
|
||||
for f in funcs {
|
||||
let name = f.name(db);
|
||||
let mut msg = format!("processing: {}", name);
|
||||
let full_name = f
|
||||
.module(db)
|
||||
.path_to_root(db)
|
||||
.into_iter()
|
||||
.rev()
|
||||
.filter_map(|it| it.name(db))
|
||||
.chain(Some(f.name(db)))
|
||||
.join("::");
|
||||
if let Some(only_name) = only {
|
||||
if name.to_string() != only_name && full_name != only_name {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let mut msg = format!("processing: {}", full_name);
|
||||
if verbosity.is_verbose() {
|
||||
let src = f.source(db);
|
||||
let original_file = src.file_id.original_file(db);
|
||||
|
@ -100,15 +129,15 @@ pub fn run(
|
|||
let syntax_range = src.value.syntax().text_range();
|
||||
write!(msg, " ({:?} {})", path, syntax_range).unwrap();
|
||||
}
|
||||
bar.set_message(&msg);
|
||||
if let Some(only_name) = only {
|
||||
if name.to_string() != only_name {
|
||||
continue;
|
||||
}
|
||||
if verbosity.is_spammy() {
|
||||
bar.println(format!("{}", msg));
|
||||
}
|
||||
bar.set_message(&msg);
|
||||
let f_id = FunctionId::from(f);
|
||||
let body = db.body(f_id.into());
|
||||
let inference_result = db.infer(f_id.into());
|
||||
let (previous_exprs, previous_unknown, previous_partially_unknown) =
|
||||
(num_exprs, num_exprs_unknown, num_exprs_partially_unknown);
|
||||
for (expr_id, _) in body.exprs.iter() {
|
||||
let ty = &inference_result[expr_id];
|
||||
num_exprs += 1;
|
||||
|
@ -125,6 +154,33 @@ pub fn run(
|
|||
num_exprs_partially_unknown += 1;
|
||||
}
|
||||
}
|
||||
if only.is_some() && verbosity.is_spammy() {
|
||||
// in super-verbose mode for just one function, we print every single expression
|
||||
let (_, sm) = db.body_with_source_map(f_id.into());
|
||||
let src = sm.expr_syntax(expr_id);
|
||||
if let Some(src) = src {
|
||||
let original_file = src.file_id.original_file(db);
|
||||
let line_index = host.analysis().file_line_index(original_file).unwrap();
|
||||
let text_range = src.value.either(
|
||||
|it| it.syntax_node_ptr().range(),
|
||||
|it| it.syntax_node_ptr().range(),
|
||||
);
|
||||
let (start, end) = (
|
||||
line_index.line_col(text_range.start()),
|
||||
line_index.line_col(text_range.end()),
|
||||
);
|
||||
bar.println(format!(
|
||||
"{}:{}-{}:{}: {}",
|
||||
start.line + 1,
|
||||
start.col_utf16,
|
||||
end.line + 1,
|
||||
end.col_utf16,
|
||||
ty.display(db)
|
||||
));
|
||||
} else {
|
||||
bar.println(format!("unknown location: {}", ty.display(db)));
|
||||
}
|
||||
}
|
||||
if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) {
|
||||
num_type_mismatches += 1;
|
||||
if verbosity.is_verbose() {
|
||||
|
@ -164,6 +220,15 @@ pub fn run(
|
|||
}
|
||||
}
|
||||
}
|
||||
if verbosity.is_spammy() {
|
||||
bar.println(format!(
|
||||
"In {}: {} exprs, {} unknown, {} partial",
|
||||
full_name,
|
||||
num_exprs - previous_exprs,
|
||||
num_exprs_unknown - previous_unknown,
|
||||
num_exprs_partially_unknown - previous_partially_unknown
|
||||
));
|
||||
}
|
||||
bar.inc(1);
|
||||
}
|
||||
bar.finish_and_clear();
|
||||
|
|
|
@ -16,6 +16,7 @@ type Result<T> = std::result::Result<T, Box<dyn Error + Send + Sync>>;
|
|||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Verbosity {
|
||||
Spammy,
|
||||
Verbose,
|
||||
Normal,
|
||||
Quiet,
|
||||
|
@ -24,7 +25,13 @@ pub enum Verbosity {
|
|||
impl Verbosity {
|
||||
fn is_verbose(self) -> bool {
|
||||
match self {
|
||||
Verbosity::Verbose => true,
|
||||
Verbosity::Verbose | Verbosity::Spammy => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
fn is_spammy(self) -> bool {
|
||||
match self {
|
||||
Verbosity::Spammy => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -86,14 +93,18 @@ fn main() -> Result<()> {
|
|||
return Ok(());
|
||||
}
|
||||
let verbosity = match (
|
||||
matches.contains(["-vv", "--spammy"]),
|
||||
matches.contains(["-v", "--verbose"]),
|
||||
matches.contains(["-q", "--quiet"]),
|
||||
) {
|
||||
(false, false) => Verbosity::Normal,
|
||||
(false, true) => Verbosity::Quiet,
|
||||
(true, false) => Verbosity::Verbose,
|
||||
(true, true) => Err("Invalid flags: -q conflicts with -v")?,
|
||||
(true, _, true) => Err("Invalid flags: -q conflicts with -vv")?,
|
||||
(true, _, false) => Verbosity::Spammy,
|
||||
(false, false, false) => Verbosity::Normal,
|
||||
(false, false, true) => Verbosity::Quiet,
|
||||
(false, true, false) => Verbosity::Verbose,
|
||||
(false, true, true) => Err("Invalid flags: -q conflicts with -v")?,
|
||||
};
|
||||
let randomize = matches.contains("--randomize");
|
||||
let memory_usage = matches.contains("--memory-usage");
|
||||
let only: Option<String> = matches.opt_value_from_str(["-o", "--only"])?;
|
||||
let with_deps: bool = matches.contains("--with-deps");
|
||||
|
@ -111,6 +122,7 @@ fn main() -> Result<()> {
|
|||
path.as_ref(),
|
||||
only.as_ref().map(String::as_ref),
|
||||
with_deps,
|
||||
randomize,
|
||||
)?;
|
||||
}
|
||||
"analysis-bench" => {
|
||||
|
|
|
@ -146,7 +146,7 @@ where
|
|||
ReachedFixedPoint::Yes => break,
|
||||
ReachedFixedPoint::No => i += 1,
|
||||
}
|
||||
if i == 1000 {
|
||||
if i == 10000 {
|
||||
log::error!("name resolution is stuck");
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -542,11 +542,7 @@ impl Resolver {
|
|||
|
||||
fn push_generic_params_scope(self, db: &impl DefDatabase, def: GenericDefId) -> Resolver {
|
||||
let params = db.generic_params(def);
|
||||
if params.types.is_empty() {
|
||||
self
|
||||
} else {
|
||||
self.push_scope(Scope::GenericParams { def, params })
|
||||
}
|
||||
self.push_scope(Scope::GenericParams { def, params })
|
||||
}
|
||||
|
||||
fn push_impl_block_scope(self, impl_block: ImplId) -> Resolver {
|
||||
|
|
|
@ -2,7 +2,12 @@
|
|||
|
||||
use std::fmt;
|
||||
|
||||
use crate::db::HirDatabase;
|
||||
use crate::{
|
||||
db::HirDatabase, utils::generics, ApplicationTy, CallableDef, FnSig, GenericPredicate,
|
||||
Obligation, ProjectionTy, Substs, TraitRef, Ty, TypeCtor,
|
||||
};
|
||||
use hir_def::{generics::TypeParamProvenance, AdtId, AssocContainerId, Lookup};
|
||||
use hir_expand::name::Name;
|
||||
|
||||
pub struct HirFormatter<'a, 'b, DB> {
|
||||
pub db: &'a DB,
|
||||
|
@ -97,3 +102,369 @@ where
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
const TYPE_HINT_TRUNCATION: &str = "…";
|
||||
|
||||
impl HirDisplay for &Ty {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
|
||||
HirDisplay::hir_fmt(*self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl HirDisplay for ApplicationTy {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
|
||||
if f.should_truncate() {
|
||||
return write!(f, "{}", TYPE_HINT_TRUNCATION);
|
||||
}
|
||||
|
||||
match self.ctor {
|
||||
TypeCtor::Bool => write!(f, "bool")?,
|
||||
TypeCtor::Char => write!(f, "char")?,
|
||||
TypeCtor::Int(t) => write!(f, "{}", t)?,
|
||||
TypeCtor::Float(t) => write!(f, "{}", t)?,
|
||||
TypeCtor::Str => write!(f, "str")?,
|
||||
TypeCtor::Slice => {
|
||||
let t = self.parameters.as_single();
|
||||
write!(f, "[{}]", t.display(f.db))?;
|
||||
}
|
||||
TypeCtor::Array => {
|
||||
let t = self.parameters.as_single();
|
||||
write!(f, "[{}; _]", t.display(f.db))?;
|
||||
}
|
||||
TypeCtor::RawPtr(m) => {
|
||||
let t = self.parameters.as_single();
|
||||
write!(f, "*{}{}", m.as_keyword_for_ptr(), t.display(f.db))?;
|
||||
}
|
||||
TypeCtor::Ref(m) => {
|
||||
let t = self.parameters.as_single();
|
||||
let ty_display = if f.omit_verbose_types() {
|
||||
t.display_truncated(f.db, f.max_size)
|
||||
} else {
|
||||
t.display(f.db)
|
||||
};
|
||||
write!(f, "&{}{}", m.as_keyword_for_ref(), ty_display)?;
|
||||
}
|
||||
TypeCtor::Never => write!(f, "!")?,
|
||||
TypeCtor::Tuple { .. } => {
|
||||
let ts = &self.parameters;
|
||||
if ts.len() == 1 {
|
||||
write!(f, "({},)", ts[0].display(f.db))?;
|
||||
} else {
|
||||
write!(f, "(")?;
|
||||
f.write_joined(&*ts.0, ", ")?;
|
||||
write!(f, ")")?;
|
||||
}
|
||||
}
|
||||
TypeCtor::FnPtr { .. } => {
|
||||
let sig = FnSig::from_fn_ptr_substs(&self.parameters);
|
||||
write!(f, "fn(")?;
|
||||
f.write_joined(sig.params(), ", ")?;
|
||||
write!(f, ") -> {}", sig.ret().display(f.db))?;
|
||||
}
|
||||
TypeCtor::FnDef(def) => {
|
||||
let sig = f.db.callable_item_signature(def).subst(&self.parameters);
|
||||
let name = match def {
|
||||
CallableDef::FunctionId(ff) => f.db.function_data(ff).name.clone(),
|
||||
CallableDef::StructId(s) => f.db.struct_data(s).name.clone(),
|
||||
CallableDef::EnumVariantId(e) => {
|
||||
let enum_data = f.db.enum_data(e.parent);
|
||||
enum_data.variants[e.local_id].name.clone()
|
||||
}
|
||||
};
|
||||
match def {
|
||||
CallableDef::FunctionId(_) => write!(f, "fn {}", name)?,
|
||||
CallableDef::StructId(_) | CallableDef::EnumVariantId(_) => {
|
||||
write!(f, "{}", name)?
|
||||
}
|
||||
}
|
||||
if self.parameters.len() > 0 {
|
||||
let generics = generics(f.db, def.into());
|
||||
let (parent_params, self_param, type_params, _impl_trait_params) =
|
||||
generics.provenance_split();
|
||||
let total_len = parent_params + self_param + type_params;
|
||||
// We print all params except implicit impl Trait params. Still a bit weird; should we leave out parent and self?
|
||||
if total_len > 0 {
|
||||
write!(f, "<")?;
|
||||
f.write_joined(&self.parameters.0[..total_len], ", ")?;
|
||||
write!(f, ">")?;
|
||||
}
|
||||
}
|
||||
write!(f, "(")?;
|
||||
f.write_joined(sig.params(), ", ")?;
|
||||
write!(f, ") -> {}", sig.ret().display(f.db))?;
|
||||
}
|
||||
TypeCtor::Adt(def_id) => {
|
||||
let name = match def_id {
|
||||
AdtId::StructId(it) => f.db.struct_data(it).name.clone(),
|
||||
AdtId::UnionId(it) => f.db.union_data(it).name.clone(),
|
||||
AdtId::EnumId(it) => f.db.enum_data(it).name.clone(),
|
||||
};
|
||||
write!(f, "{}", name)?;
|
||||
if self.parameters.len() > 0 {
|
||||
write!(f, "<")?;
|
||||
|
||||
let mut non_default_parameters = Vec::with_capacity(self.parameters.len());
|
||||
let parameters_to_write = if f.omit_verbose_types() {
|
||||
match self
|
||||
.ctor
|
||||
.as_generic_def()
|
||||
.map(|generic_def_id| f.db.generic_defaults(generic_def_id))
|
||||
.filter(|defaults| !defaults.is_empty())
|
||||
{
|
||||
Option::None => self.parameters.0.as_ref(),
|
||||
Option::Some(default_parameters) => {
|
||||
for (i, parameter) in self.parameters.iter().enumerate() {
|
||||
match (parameter, default_parameters.get(i)) {
|
||||
(&Ty::Unknown, _) | (_, None) => {
|
||||
non_default_parameters.push(parameter.clone())
|
||||
}
|
||||
(_, Some(default_parameter))
|
||||
if parameter != default_parameter =>
|
||||
{
|
||||
non_default_parameters.push(parameter.clone())
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
&non_default_parameters
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.parameters.0.as_ref()
|
||||
};
|
||||
|
||||
f.write_joined(parameters_to_write, ", ")?;
|
||||
write!(f, ">")?;
|
||||
}
|
||||
}
|
||||
TypeCtor::AssociatedType(type_alias) => {
|
||||
let trait_ = match type_alias.lookup(f.db).container {
|
||||
AssocContainerId::TraitId(it) => it,
|
||||
_ => panic!("not an associated type"),
|
||||
};
|
||||
let trait_name = f.db.trait_data(trait_).name.clone();
|
||||
let name = f.db.type_alias_data(type_alias).name.clone();
|
||||
write!(f, "{}::{}", trait_name, name)?;
|
||||
if self.parameters.len() > 0 {
|
||||
write!(f, "<")?;
|
||||
f.write_joined(&*self.parameters.0, ", ")?;
|
||||
write!(f, ">")?;
|
||||
}
|
||||
}
|
||||
TypeCtor::Closure { .. } => {
|
||||
let sig = self.parameters[0]
|
||||
.callable_sig(f.db)
|
||||
.expect("first closure parameter should contain signature");
|
||||
let return_type_hint = sig.ret().display(f.db);
|
||||
if sig.params().is_empty() {
|
||||
write!(f, "|| -> {}", return_type_hint)?;
|
||||
} else if f.omit_verbose_types() {
|
||||
write!(f, "|{}| -> {}", TYPE_HINT_TRUNCATION, return_type_hint)?;
|
||||
} else {
|
||||
write!(f, "|")?;
|
||||
f.write_joined(sig.params(), ", ")?;
|
||||
write!(f, "| -> {}", return_type_hint)?;
|
||||
};
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HirDisplay for ProjectionTy {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
|
||||
if f.should_truncate() {
|
||||
return write!(f, "{}", TYPE_HINT_TRUNCATION);
|
||||
}
|
||||
|
||||
let trait_name = f.db.trait_data(self.trait_(f.db)).name.clone();
|
||||
write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_name,)?;
|
||||
if self.parameters.len() > 1 {
|
||||
write!(f, "<")?;
|
||||
f.write_joined(&self.parameters[1..], ", ")?;
|
||||
write!(f, ">")?;
|
||||
}
|
||||
write!(f, ">::{}", f.db.type_alias_data(self.associated_ty).name)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HirDisplay for Ty {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
|
||||
if f.should_truncate() {
|
||||
return write!(f, "{}", TYPE_HINT_TRUNCATION);
|
||||
}
|
||||
|
||||
match self {
|
||||
Ty::Apply(a_ty) => a_ty.hir_fmt(f)?,
|
||||
Ty::Projection(p_ty) => p_ty.hir_fmt(f)?,
|
||||
Ty::Placeholder(id) => {
|
||||
let generics = generics(f.db, id.parent);
|
||||
let param_data = &generics.params.types[id.local_id];
|
||||
match param_data.provenance {
|
||||
TypeParamProvenance::TypeParamList | TypeParamProvenance::TraitSelf => {
|
||||
write!(f, "{}", param_data.name.clone().unwrap_or_else(Name::missing))?
|
||||
}
|
||||
TypeParamProvenance::ArgumentImplTrait => {
|
||||
write!(f, "impl ")?;
|
||||
let bounds = f.db.generic_predicates_for_param(*id);
|
||||
let substs = Substs::type_params_for_generics(&generics);
|
||||
write_bounds_like_dyn_trait(
|
||||
&bounds.iter().map(|b| b.clone().subst(&substs)).collect::<Vec<_>>(),
|
||||
f,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ty::Bound(idx) => write!(f, "?{}", idx)?,
|
||||
Ty::Dyn(predicates) | Ty::Opaque(predicates) => {
|
||||
match self {
|
||||
Ty::Dyn(_) => write!(f, "dyn ")?,
|
||||
Ty::Opaque(_) => write!(f, "impl ")?,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
write_bounds_like_dyn_trait(&predicates, f)?;
|
||||
}
|
||||
Ty::Unknown => write!(f, "{{unknown}}")?,
|
||||
Ty::Infer(..) => write!(f, "_")?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_bounds_like_dyn_trait(
|
||||
predicates: &[GenericPredicate],
|
||||
f: &mut HirFormatter<impl HirDatabase>,
|
||||
) -> fmt::Result {
|
||||
// Note: This code is written to produce nice results (i.e.
|
||||
// corresponding to surface Rust) for types that can occur in
|
||||
// actual Rust. It will have weird results if the predicates
|
||||
// aren't as expected (i.e. self types = $0, projection
|
||||
// predicates for a certain trait come after the Implemented
|
||||
// predicate for that trait).
|
||||
let mut first = true;
|
||||
let mut angle_open = false;
|
||||
for p in predicates.iter() {
|
||||
match p {
|
||||
GenericPredicate::Implemented(trait_ref) => {
|
||||
if angle_open {
|
||||
write!(f, ">")?;
|
||||
}
|
||||
if !first {
|
||||
write!(f, " + ")?;
|
||||
}
|
||||
// We assume that the self type is $0 (i.e. the
|
||||
// existential) here, which is the only thing that's
|
||||
// possible in actual Rust, and hence don't print it
|
||||
write!(f, "{}", f.db.trait_data(trait_ref.trait_).name.clone())?;
|
||||
if trait_ref.substs.len() > 1 {
|
||||
write!(f, "<")?;
|
||||
f.write_joined(&trait_ref.substs[1..], ", ")?;
|
||||
// there might be assoc type bindings, so we leave the angle brackets open
|
||||
angle_open = true;
|
||||
}
|
||||
}
|
||||
GenericPredicate::Projection(projection_pred) => {
|
||||
// in types in actual Rust, these will always come
|
||||
// after the corresponding Implemented predicate
|
||||
if angle_open {
|
||||
write!(f, ", ")?;
|
||||
} else {
|
||||
write!(f, "<")?;
|
||||
angle_open = true;
|
||||
}
|
||||
let name =
|
||||
f.db.type_alias_data(projection_pred.projection_ty.associated_ty).name.clone();
|
||||
write!(f, "{} = ", name)?;
|
||||
projection_pred.ty.hir_fmt(f)?;
|
||||
}
|
||||
GenericPredicate::Error => {
|
||||
if angle_open {
|
||||
// impl Trait<X, {error}>
|
||||
write!(f, ", ")?;
|
||||
} else if !first {
|
||||
// impl Trait + {error}
|
||||
write!(f, " + ")?;
|
||||
}
|
||||
p.hir_fmt(f)?;
|
||||
}
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
if angle_open {
|
||||
write!(f, ">")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl TraitRef {
|
||||
fn hir_fmt_ext(&self, f: &mut HirFormatter<impl HirDatabase>, use_as: bool) -> fmt::Result {
|
||||
if f.should_truncate() {
|
||||
return write!(f, "{}", TYPE_HINT_TRUNCATION);
|
||||
}
|
||||
|
||||
self.substs[0].hir_fmt(f)?;
|
||||
if use_as {
|
||||
write!(f, " as ")?;
|
||||
} else {
|
||||
write!(f, ": ")?;
|
||||
}
|
||||
write!(f, "{}", f.db.trait_data(self.trait_).name.clone())?;
|
||||
if self.substs.len() > 1 {
|
||||
write!(f, "<")?;
|
||||
f.write_joined(&self.substs[1..], ", ")?;
|
||||
write!(f, ">")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HirDisplay for TraitRef {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
|
||||
self.hir_fmt_ext(f, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl HirDisplay for &GenericPredicate {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
|
||||
HirDisplay::hir_fmt(*self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl HirDisplay for GenericPredicate {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
|
||||
if f.should_truncate() {
|
||||
return write!(f, "{}", TYPE_HINT_TRUNCATION);
|
||||
}
|
||||
|
||||
match self {
|
||||
GenericPredicate::Implemented(trait_ref) => trait_ref.hir_fmt(f)?,
|
||||
GenericPredicate::Projection(projection_pred) => {
|
||||
write!(f, "<")?;
|
||||
projection_pred.projection_ty.trait_ref(f.db).hir_fmt_ext(f, true)?;
|
||||
write!(
|
||||
f,
|
||||
">::{} = {}",
|
||||
f.db.type_alias_data(projection_pred.projection_ty.associated_ty).name,
|
||||
projection_pred.ty.display(f.db)
|
||||
)?;
|
||||
}
|
||||
GenericPredicate::Error => write!(f, "{{error}}")?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HirDisplay for Obligation {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
|
||||
match self {
|
||||
Obligation::Trait(tr) => write!(f, "Implements({})", tr.display(f.db)),
|
||||
Obligation::Projection(proj) => write!(
|
||||
f,
|
||||
"Normalize({} => {})",
|
||||
proj.projection_ty.display(f.db),
|
||||
proj.ty.display(f.db)
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -249,6 +249,8 @@ impl InferenceTable {
|
|||
match (ty1, ty2) {
|
||||
(Ty::Unknown, _) | (_, Ty::Unknown) => true,
|
||||
|
||||
(Ty::Placeholder(p1), Ty::Placeholder(p2)) if *p1 == *p2 => true,
|
||||
|
||||
(Ty::Infer(InferTy::TypeVar(tv1)), Ty::Infer(InferTy::TypeVar(tv2)))
|
||||
| (Ty::Infer(InferTy::IntVar(tv1)), Ty::Infer(InferTy::IntVar(tv2)))
|
||||
| (Ty::Infer(InferTy::FloatVar(tv1)), Ty::Infer(InferTy::FloatVar(tv2)))
|
||||
|
|
|
@ -41,13 +41,12 @@ mod marks;
|
|||
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, iter, mem};
|
||||
use std::{iter, mem};
|
||||
|
||||
use hir_def::{
|
||||
expr::ExprId, generics::TypeParamProvenance, type_ref::Mutability, AdtId, AssocContainerId,
|
||||
DefWithBodyId, GenericDefId, HasModule, Lookup, TraitId, TypeAliasId, TypeParamId,
|
||||
expr::ExprId, type_ref::Mutability, AdtId, AssocContainerId, DefWithBodyId, GenericDefId,
|
||||
HasModule, Lookup, TraitId, TypeAliasId, TypeParamId,
|
||||
};
|
||||
use hir_expand::name::Name;
|
||||
use ra_db::{impl_intern_key, salsa, CrateId};
|
||||
|
||||
use crate::{
|
||||
|
@ -55,7 +54,7 @@ use crate::{
|
|||
primitive::{FloatTy, IntTy, Uncertain},
|
||||
utils::{generics, make_mut_slice, Generics},
|
||||
};
|
||||
use display::{HirDisplay, HirFormatter};
|
||||
use display::HirDisplay;
|
||||
|
||||
pub use autoderef::autoderef;
|
||||
pub use infer::{do_infer_query, InferTy, InferenceResult};
|
||||
|
@ -291,7 +290,7 @@ pub enum Ty {
|
|||
/// {}` when we're type-checking the body of that function. In this
|
||||
/// situation, we know this stands for *some* type, but don't know the exact
|
||||
/// type.
|
||||
Param(TypeParamId),
|
||||
Placeholder(TypeParamId),
|
||||
|
||||
/// A bound type variable. This is used in various places: when representing
|
||||
/// some polymorphic type like the type of function `fn f<T>`, the type
|
||||
|
@ -365,7 +364,7 @@ impl Substs {
|
|||
|
||||
/// Return Substs that replace each parameter by itself (i.e. `Ty::Param`).
|
||||
pub(crate) fn type_params_for_generics(generic_params: &Generics) -> Substs {
|
||||
Substs(generic_params.iter().map(|(id, _)| Ty::Param(id)).collect())
|
||||
Substs(generic_params.iter().map(|(id, _)| Ty::Placeholder(id)).collect())
|
||||
}
|
||||
|
||||
/// Return Substs that replace each parameter by itself (i.e. `Ty::Param`).
|
||||
|
@ -813,7 +812,7 @@ impl TypeWalk for Ty {
|
|||
p.walk(f);
|
||||
}
|
||||
}
|
||||
Ty::Param { .. } | Ty::Bound(_) | Ty::Infer(_) | Ty::Unknown => {}
|
||||
Ty::Placeholder { .. } | Ty::Bound(_) | Ty::Infer(_) | Ty::Unknown => {}
|
||||
}
|
||||
f(self);
|
||||
}
|
||||
|
@ -831,374 +830,8 @@ impl TypeWalk for Ty {
|
|||
p.walk_mut_binders(f, binders + 1);
|
||||
}
|
||||
}
|
||||
Ty::Param { .. } | Ty::Bound(_) | Ty::Infer(_) | Ty::Unknown => {}
|
||||
Ty::Placeholder { .. } | Ty::Bound(_) | Ty::Infer(_) | Ty::Unknown => {}
|
||||
}
|
||||
f(self, binders);
|
||||
}
|
||||
}
|
||||
|
||||
const TYPE_HINT_TRUNCATION: &str = "…";
|
||||
|
||||
impl HirDisplay for &Ty {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
|
||||
HirDisplay::hir_fmt(*self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl HirDisplay for ApplicationTy {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
|
||||
if f.should_truncate() {
|
||||
return write!(f, "{}", TYPE_HINT_TRUNCATION);
|
||||
}
|
||||
|
||||
match self.ctor {
|
||||
TypeCtor::Bool => write!(f, "bool")?,
|
||||
TypeCtor::Char => write!(f, "char")?,
|
||||
TypeCtor::Int(t) => write!(f, "{}", t)?,
|
||||
TypeCtor::Float(t) => write!(f, "{}", t)?,
|
||||
TypeCtor::Str => write!(f, "str")?,
|
||||
TypeCtor::Slice => {
|
||||
let t = self.parameters.as_single();
|
||||
write!(f, "[{}]", t.display(f.db))?;
|
||||
}
|
||||
TypeCtor::Array => {
|
||||
let t = self.parameters.as_single();
|
||||
write!(f, "[{}; _]", t.display(f.db))?;
|
||||
}
|
||||
TypeCtor::RawPtr(m) => {
|
||||
let t = self.parameters.as_single();
|
||||
write!(f, "*{}{}", m.as_keyword_for_ptr(), t.display(f.db))?;
|
||||
}
|
||||
TypeCtor::Ref(m) => {
|
||||
let t = self.parameters.as_single();
|
||||
let ty_display = if f.omit_verbose_types() {
|
||||
t.display_truncated(f.db, f.max_size)
|
||||
} else {
|
||||
t.display(f.db)
|
||||
};
|
||||
write!(f, "&{}{}", m.as_keyword_for_ref(), ty_display)?;
|
||||
}
|
||||
TypeCtor::Never => write!(f, "!")?,
|
||||
TypeCtor::Tuple { .. } => {
|
||||
let ts = &self.parameters;
|
||||
if ts.len() == 1 {
|
||||
write!(f, "({},)", ts[0].display(f.db))?;
|
||||
} else {
|
||||
write!(f, "(")?;
|
||||
f.write_joined(&*ts.0, ", ")?;
|
||||
write!(f, ")")?;
|
||||
}
|
||||
}
|
||||
TypeCtor::FnPtr { .. } => {
|
||||
let sig = FnSig::from_fn_ptr_substs(&self.parameters);
|
||||
write!(f, "fn(")?;
|
||||
f.write_joined(sig.params(), ", ")?;
|
||||
write!(f, ") -> {}", sig.ret().display(f.db))?;
|
||||
}
|
||||
TypeCtor::FnDef(def) => {
|
||||
let sig = f.db.callable_item_signature(def).subst(&self.parameters);
|
||||
let name = match def {
|
||||
CallableDef::FunctionId(ff) => f.db.function_data(ff).name.clone(),
|
||||
CallableDef::StructId(s) => f.db.struct_data(s).name.clone(),
|
||||
CallableDef::EnumVariantId(e) => {
|
||||
let enum_data = f.db.enum_data(e.parent);
|
||||
enum_data.variants[e.local_id].name.clone()
|
||||
}
|
||||
};
|
||||
match def {
|
||||
CallableDef::FunctionId(_) => write!(f, "fn {}", name)?,
|
||||
CallableDef::StructId(_) | CallableDef::EnumVariantId(_) => {
|
||||
write!(f, "{}", name)?
|
||||
}
|
||||
}
|
||||
if self.parameters.len() > 0 {
|
||||
let generics = generics(f.db, def.into());
|
||||
let (parent_params, self_param, type_params, _impl_trait_params) =
|
||||
generics.provenance_split();
|
||||
let total_len = parent_params + self_param + type_params;
|
||||
// We print all params except implicit impl Trait params. Still a bit weird; should we leave out parent and self?
|
||||
if total_len > 0 {
|
||||
write!(f, "<")?;
|
||||
f.write_joined(&self.parameters.0[..total_len], ", ")?;
|
||||
write!(f, ">")?;
|
||||
}
|
||||
}
|
||||
write!(f, "(")?;
|
||||
f.write_joined(sig.params(), ", ")?;
|
||||
write!(f, ") -> {}", sig.ret().display(f.db))?;
|
||||
}
|
||||
TypeCtor::Adt(def_id) => {
|
||||
let name = match def_id {
|
||||
AdtId::StructId(it) => f.db.struct_data(it).name.clone(),
|
||||
AdtId::UnionId(it) => f.db.union_data(it).name.clone(),
|
||||
AdtId::EnumId(it) => f.db.enum_data(it).name.clone(),
|
||||
};
|
||||
write!(f, "{}", name)?;
|
||||
if self.parameters.len() > 0 {
|
||||
write!(f, "<")?;
|
||||
|
||||
let mut non_default_parameters = Vec::with_capacity(self.parameters.len());
|
||||
let parameters_to_write = if f.omit_verbose_types() {
|
||||
match self
|
||||
.ctor
|
||||
.as_generic_def()
|
||||
.map(|generic_def_id| f.db.generic_defaults(generic_def_id))
|
||||
.filter(|defaults| !defaults.is_empty())
|
||||
{
|
||||
Option::None => self.parameters.0.as_ref(),
|
||||
Option::Some(default_parameters) => {
|
||||
for (i, parameter) in self.parameters.iter().enumerate() {
|
||||
match (parameter, default_parameters.get(i)) {
|
||||
(&Ty::Unknown, _) | (_, None) => {
|
||||
non_default_parameters.push(parameter.clone())
|
||||
}
|
||||
(_, Some(default_parameter))
|
||||
if parameter != default_parameter =>
|
||||
{
|
||||
non_default_parameters.push(parameter.clone())
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
&non_default_parameters
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.parameters.0.as_ref()
|
||||
};
|
||||
|
||||
f.write_joined(parameters_to_write, ", ")?;
|
||||
write!(f, ">")?;
|
||||
}
|
||||
}
|
||||
TypeCtor::AssociatedType(type_alias) => {
|
||||
let trait_ = match type_alias.lookup(f.db).container {
|
||||
AssocContainerId::TraitId(it) => it,
|
||||
_ => panic!("not an associated type"),
|
||||
};
|
||||
let trait_name = f.db.trait_data(trait_).name.clone();
|
||||
let name = f.db.type_alias_data(type_alias).name.clone();
|
||||
write!(f, "{}::{}", trait_name, name)?;
|
||||
if self.parameters.len() > 0 {
|
||||
write!(f, "<")?;
|
||||
f.write_joined(&*self.parameters.0, ", ")?;
|
||||
write!(f, ">")?;
|
||||
}
|
||||
}
|
||||
TypeCtor::Closure { .. } => {
|
||||
let sig = self.parameters[0]
|
||||
.callable_sig(f.db)
|
||||
.expect("first closure parameter should contain signature");
|
||||
let return_type_hint = sig.ret().display(f.db);
|
||||
if sig.params().is_empty() {
|
||||
write!(f, "|| -> {}", return_type_hint)?;
|
||||
} else if f.omit_verbose_types() {
|
||||
write!(f, "|{}| -> {}", TYPE_HINT_TRUNCATION, return_type_hint)?;
|
||||
} else {
|
||||
write!(f, "|")?;
|
||||
f.write_joined(sig.params(), ", ")?;
|
||||
write!(f, "| -> {}", return_type_hint)?;
|
||||
};
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HirDisplay for ProjectionTy {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
|
||||
if f.should_truncate() {
|
||||
return write!(f, "{}", TYPE_HINT_TRUNCATION);
|
||||
}
|
||||
|
||||
let trait_name = f.db.trait_data(self.trait_(f.db)).name.clone();
|
||||
write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_name,)?;
|
||||
if self.parameters.len() > 1 {
|
||||
write!(f, "<")?;
|
||||
f.write_joined(&self.parameters[1..], ", ")?;
|
||||
write!(f, ">")?;
|
||||
}
|
||||
write!(f, ">::{}", f.db.type_alias_data(self.associated_ty).name)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HirDisplay for Ty {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
|
||||
if f.should_truncate() {
|
||||
return write!(f, "{}", TYPE_HINT_TRUNCATION);
|
||||
}
|
||||
|
||||
match self {
|
||||
Ty::Apply(a_ty) => a_ty.hir_fmt(f)?,
|
||||
Ty::Projection(p_ty) => p_ty.hir_fmt(f)?,
|
||||
Ty::Param(id) => {
|
||||
let generics = generics(f.db, id.parent);
|
||||
let param_data = &generics.params.types[id.local_id];
|
||||
match param_data.provenance {
|
||||
TypeParamProvenance::TypeParamList | TypeParamProvenance::TraitSelf => {
|
||||
write!(f, "{}", param_data.name.clone().unwrap_or_else(Name::missing))?
|
||||
}
|
||||
TypeParamProvenance::ArgumentImplTrait => {
|
||||
write!(f, "impl ")?;
|
||||
let bounds = f.db.generic_predicates_for_param(*id);
|
||||
let substs = Substs::type_params_for_generics(&generics);
|
||||
write_bounds_like_dyn_trait(
|
||||
&bounds.iter().map(|b| b.clone().subst(&substs)).collect::<Vec<_>>(),
|
||||
f,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ty::Bound(idx) => write!(f, "?{}", idx)?,
|
||||
Ty::Dyn(predicates) | Ty::Opaque(predicates) => {
|
||||
match self {
|
||||
Ty::Dyn(_) => write!(f, "dyn ")?,
|
||||
Ty::Opaque(_) => write!(f, "impl ")?,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
write_bounds_like_dyn_trait(&predicates, f)?;
|
||||
}
|
||||
Ty::Unknown => write!(f, "{{unknown}}")?,
|
||||
Ty::Infer(..) => write!(f, "_")?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_bounds_like_dyn_trait(
|
||||
predicates: &[GenericPredicate],
|
||||
f: &mut HirFormatter<impl HirDatabase>,
|
||||
) -> fmt::Result {
|
||||
// Note: This code is written to produce nice results (i.e.
|
||||
// corresponding to surface Rust) for types that can occur in
|
||||
// actual Rust. It will have weird results if the predicates
|
||||
// aren't as expected (i.e. self types = $0, projection
|
||||
// predicates for a certain trait come after the Implemented
|
||||
// predicate for that trait).
|
||||
let mut first = true;
|
||||
let mut angle_open = false;
|
||||
for p in predicates.iter() {
|
||||
match p {
|
||||
GenericPredicate::Implemented(trait_ref) => {
|
||||
if angle_open {
|
||||
write!(f, ">")?;
|
||||
}
|
||||
if !first {
|
||||
write!(f, " + ")?;
|
||||
}
|
||||
// We assume that the self type is $0 (i.e. the
|
||||
// existential) here, which is the only thing that's
|
||||
// possible in actual Rust, and hence don't print it
|
||||
write!(f, "{}", f.db.trait_data(trait_ref.trait_).name.clone())?;
|
||||
if trait_ref.substs.len() > 1 {
|
||||
write!(f, "<")?;
|
||||
f.write_joined(&trait_ref.substs[1..], ", ")?;
|
||||
// there might be assoc type bindings, so we leave the angle brackets open
|
||||
angle_open = true;
|
||||
}
|
||||
}
|
||||
GenericPredicate::Projection(projection_pred) => {
|
||||
// in types in actual Rust, these will always come
|
||||
// after the corresponding Implemented predicate
|
||||
if angle_open {
|
||||
write!(f, ", ")?;
|
||||
} else {
|
||||
write!(f, "<")?;
|
||||
angle_open = true;
|
||||
}
|
||||
let name =
|
||||
f.db.type_alias_data(projection_pred.projection_ty.associated_ty).name.clone();
|
||||
write!(f, "{} = ", name)?;
|
||||
projection_pred.ty.hir_fmt(f)?;
|
||||
}
|
||||
GenericPredicate::Error => {
|
||||
if angle_open {
|
||||
// impl Trait<X, {error}>
|
||||
write!(f, ", ")?;
|
||||
} else if !first {
|
||||
// impl Trait + {error}
|
||||
write!(f, " + ")?;
|
||||
}
|
||||
p.hir_fmt(f)?;
|
||||
}
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
if angle_open {
|
||||
write!(f, ">")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl TraitRef {
|
||||
fn hir_fmt_ext(&self, f: &mut HirFormatter<impl HirDatabase>, use_as: bool) -> fmt::Result {
|
||||
if f.should_truncate() {
|
||||
return write!(f, "{}", TYPE_HINT_TRUNCATION);
|
||||
}
|
||||
|
||||
self.substs[0].hir_fmt(f)?;
|
||||
if use_as {
|
||||
write!(f, " as ")?;
|
||||
} else {
|
||||
write!(f, ": ")?;
|
||||
}
|
||||
write!(f, "{}", f.db.trait_data(self.trait_).name.clone())?;
|
||||
if self.substs.len() > 1 {
|
||||
write!(f, "<")?;
|
||||
f.write_joined(&self.substs[1..], ", ")?;
|
||||
write!(f, ">")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HirDisplay for TraitRef {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
|
||||
self.hir_fmt_ext(f, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl HirDisplay for &GenericPredicate {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
|
||||
HirDisplay::hir_fmt(*self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl HirDisplay for GenericPredicate {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
|
||||
if f.should_truncate() {
|
||||
return write!(f, "{}", TYPE_HINT_TRUNCATION);
|
||||
}
|
||||
|
||||
match self {
|
||||
GenericPredicate::Implemented(trait_ref) => trait_ref.hir_fmt(f)?,
|
||||
GenericPredicate::Projection(projection_pred) => {
|
||||
write!(f, "<")?;
|
||||
projection_pred.projection_ty.trait_ref(f.db).hir_fmt_ext(f, true)?;
|
||||
write!(
|
||||
f,
|
||||
">::{} = {}",
|
||||
f.db.type_alias_data(projection_pred.projection_ty.associated_ty).name,
|
||||
projection_pred.ty.display(f.db)
|
||||
)?;
|
||||
}
|
||||
GenericPredicate::Error => write!(f, "{{error}}")?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HirDisplay for Obligation {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
|
||||
match self {
|
||||
Obligation::Trait(tr) => write!(f, "Implements({})", tr.display(f.db)),
|
||||
Obligation::Projection(proj) => write!(
|
||||
f,
|
||||
"Normalize({} => {})",
|
||||
proj.projection_ty.display(f.db),
|
||||
proj.ty.display(f.db)
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,9 +14,9 @@ use hir_def::{
|
|||
path::{GenericArg, Path, PathSegment, PathSegments},
|
||||
resolver::{HasResolver, Resolver, TypeNs},
|
||||
type_ref::{TypeBound, TypeRef},
|
||||
AdtId, ConstId, EnumId, EnumVariantId, FunctionId, GenericDefId, HasModule, ImplId,
|
||||
LocalStructFieldId, Lookup, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, UnionId,
|
||||
VariantId,
|
||||
AdtId, AssocContainerId, ConstId, EnumId, EnumVariantId, FunctionId, GenericDefId, HasModule,
|
||||
ImplId, LocalStructFieldId, Lookup, StaticId, StructId, TraitId, TypeAliasId, TypeParamId,
|
||||
UnionId, VariantId,
|
||||
};
|
||||
use ra_arena::map::ArenaMap;
|
||||
use ra_db::CrateId;
|
||||
|
@ -152,7 +152,7 @@ impl Ty {
|
|||
data.provenance == TypeParamProvenance::ArgumentImplTrait
|
||||
})
|
||||
.nth(idx as usize)
|
||||
.map_or(Ty::Unknown, |(id, _)| Ty::Param(id));
|
||||
.map_or(Ty::Unknown, |(id, _)| Ty::Placeholder(id));
|
||||
param
|
||||
} else {
|
||||
Ty::Unknown
|
||||
|
@ -270,7 +270,7 @@ impl Ty {
|
|||
let generics =
|
||||
generics(ctx.db, ctx.resolver.generic_def().expect("generics in scope"));
|
||||
match ctx.type_param_mode {
|
||||
TypeParamLoweringMode::Placeholder => Ty::Param(param_id),
|
||||
TypeParamLoweringMode::Placeholder => Ty::Placeholder(param_id),
|
||||
TypeParamLoweringMode::Variable => {
|
||||
let idx = generics.param_idx(param_id).expect("matching generics");
|
||||
Ty::Bound(idx)
|
||||
|
@ -339,7 +339,7 @@ impl Ty {
|
|||
None => return Ty::Unknown, // this can't actually happen
|
||||
};
|
||||
let param_id = match self_ty {
|
||||
Ty::Param(id) if ctx.type_param_mode == TypeParamLoweringMode::Placeholder => id,
|
||||
Ty::Placeholder(id) if ctx.type_param_mode == TypeParamLoweringMode::Placeholder => id,
|
||||
Ty::Bound(idx) if ctx.type_param_mode == TypeParamLoweringMode::Variable => {
|
||||
let generics = generics(ctx.db, def);
|
||||
let param_id = if let Some((id, _)) = generics.iter().nth(idx as usize) {
|
||||
|
@ -544,7 +544,7 @@ impl GenericPredicate {
|
|||
let generics = generics(ctx.db, generic_def);
|
||||
let param_id = hir_def::TypeParamId { parent: generic_def, local_id: *param_id };
|
||||
match ctx.type_param_mode {
|
||||
TypeParamLoweringMode::Placeholder => Ty::Param(param_id),
|
||||
TypeParamLoweringMode::Placeholder => Ty::Placeholder(param_id),
|
||||
TypeParamLoweringMode::Variable => {
|
||||
let idx = generics.param_idx(param_id).expect("matching generics");
|
||||
Ty::Bound(idx)
|
||||
|
@ -672,11 +672,35 @@ impl TraitEnvironment {
|
|||
pub fn lower(db: &impl HirDatabase, resolver: &Resolver) -> Arc<TraitEnvironment> {
|
||||
let ctx = TyLoweringContext::new(db, &resolver)
|
||||
.with_type_param_mode(TypeParamLoweringMode::Placeholder);
|
||||
let predicates = resolver
|
||||
let mut predicates = resolver
|
||||
.where_predicates_in_scope()
|
||||
.flat_map(|pred| GenericPredicate::from_where_predicate(&ctx, pred))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Some(def) = resolver.generic_def() {
|
||||
let container: Option<AssocContainerId> = match def {
|
||||
// FIXME: is there a function for this?
|
||||
GenericDefId::FunctionId(f) => Some(f.lookup(db).container),
|
||||
GenericDefId::AdtId(_) => None,
|
||||
GenericDefId::TraitId(_) => None,
|
||||
GenericDefId::TypeAliasId(t) => Some(t.lookup(db).container),
|
||||
GenericDefId::ImplId(_) => None,
|
||||
GenericDefId::EnumVariantId(_) => None,
|
||||
GenericDefId::ConstId(c) => Some(c.lookup(db).container),
|
||||
};
|
||||
if let Some(AssocContainerId::TraitId(trait_id)) = container {
|
||||
// add `Self: Trait<T1, T2, ...>` to the environment in trait
|
||||
// function default implementations (and hypothetical code
|
||||
// inside consts or type aliases)
|
||||
test_utils::tested_by!(trait_self_implements_self);
|
||||
let substs = Substs::type_params(db, trait_id);
|
||||
let trait_ref = TraitRef { trait_: trait_id, substs };
|
||||
let pred = GenericPredicate::Implemented(trait_ref);
|
||||
|
||||
predicates.push(pred);
|
||||
}
|
||||
}
|
||||
|
||||
Arc::new(TraitEnvironment { predicates })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ test_utils::marks!(
|
|||
type_var_cycles_resolve_completely
|
||||
type_var_cycles_resolve_as_possible
|
||||
type_var_resolves_to_int_var
|
||||
impl_self_type_match_without_receiver
|
||||
match_ergonomics_ref
|
||||
coerce_merge_fail_fallback
|
||||
trait_self_implements_self
|
||||
);
|
||||
|
|
|
@ -425,6 +425,15 @@ fn iterate_inherent_methods<T>(
|
|||
if !is_valid_candidate(db, name, receiver_ty, item, self_ty) {
|
||||
continue;
|
||||
}
|
||||
// we have to check whether the self type unifies with the type
|
||||
// that the impl is for. If we have a receiver type, this
|
||||
// already happens in `is_valid_candidate` above; if not, we
|
||||
// check it here
|
||||
if receiver_ty.is_none() && inherent_impl_substs(db, impl_block, self_ty).is_none()
|
||||
{
|
||||
test_utils::tested_by!(impl_self_type_match_without_receiver);
|
||||
continue;
|
||||
}
|
||||
if let Some(result) = callback(&self_ty.value, item) {
|
||||
return Some(result);
|
||||
}
|
||||
|
|
|
@ -526,3 +526,25 @@ fn test() {
|
|||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coerce_placeholder_ref() {
|
||||
// placeholders should unify, even behind references
|
||||
assert_snapshot!(
|
||||
infer_with_mismatches(r#"
|
||||
struct S<T> { t: T }
|
||||
impl<TT> S<TT> {
|
||||
fn get(&self) -> &TT {
|
||||
&self.t
|
||||
}
|
||||
}
|
||||
"#, true),
|
||||
@r###"
|
||||
[51; 55) 'self': &S<TT>
|
||||
[64; 87) '{ ... }': &TT
|
||||
[74; 81) '&self.t': &TT
|
||||
[75; 79) 'self': &S<TT>
|
||||
[75; 81) 'self.t': TT
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
|
|
@ -963,6 +963,38 @@ fn test() { S2.into()<|>; }
|
|||
assert_eq!(t, "{unknown}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_resolution_overloaded_method() {
|
||||
test_utils::covers!(impl_self_type_match_without_receiver);
|
||||
let t = type_at(
|
||||
r#"
|
||||
//- main.rs
|
||||
struct Wrapper<T>(T);
|
||||
struct Foo<T>(T);
|
||||
struct Bar<T>(T);
|
||||
|
||||
impl<T> Wrapper<Foo<T>> {
|
||||
pub fn new(foo_: T) -> Self {
|
||||
Wrapper(Foo(foo_))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Wrapper<Bar<T>> {
|
||||
pub fn new(bar_: T) -> Self {
|
||||
Wrapper(Bar(bar_))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let a = Wrapper::<Foo<f32>>::new(1.0);
|
||||
let b = Wrapper::<Bar<f32>>::new(1.0);
|
||||
(a, b)<|>;
|
||||
}
|
||||
"#,
|
||||
);
|
||||
assert_eq!(t, "(Wrapper<Foo<f32>>, Wrapper<Bar<f32>>)")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_resolution_encountering_fn_type() {
|
||||
type_at(
|
||||
|
|
|
@ -299,6 +299,54 @@ fn test() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trait_default_method_self_bound_implements_trait() {
|
||||
test_utils::covers!(trait_self_implements_self);
|
||||
assert_snapshot!(
|
||||
infer(r#"
|
||||
trait Trait {
|
||||
fn foo(&self) -> i64;
|
||||
fn bar(&self) -> {
|
||||
let x = self.foo();
|
||||
}
|
||||
}
|
||||
"#),
|
||||
@r###"
|
||||
[27; 31) 'self': &Self
|
||||
[53; 57) 'self': &Self
|
||||
[62; 97) '{ ... }': ()
|
||||
[76; 77) 'x': i64
|
||||
[80; 84) 'self': &Self
|
||||
[80; 90) 'self.foo()': i64
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trait_default_method_self_bound_implements_super_trait() {
|
||||
test_utils::covers!(trait_self_implements_self);
|
||||
assert_snapshot!(
|
||||
infer(r#"
|
||||
trait SuperTrait {
|
||||
fn foo(&self) -> i64;
|
||||
}
|
||||
trait Trait: SuperTrait {
|
||||
fn bar(&self) -> {
|
||||
let x = self.foo();
|
||||
}
|
||||
}
|
||||
"#),
|
||||
@r###"
|
||||
[32; 36) 'self': &Self
|
||||
[86; 90) 'self': &Self
|
||||
[95; 130) '{ ... }': ()
|
||||
[109; 110) 'x': i64
|
||||
[113; 117) 'self': &Self
|
||||
[113; 123) 'self.foo()': i64
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_project_associated_type() {
|
||||
// y, z, a don't yet work because of https://github.com/rust-lang/chalk/issues/234
|
||||
|
|
|
@ -60,6 +60,9 @@ impl TraitSolver {
|
|||
context.0.db.check_canceled();
|
||||
let remaining = fuel.get();
|
||||
fuel.set(remaining - 1);
|
||||
if remaining == 0 {
|
||||
log::debug!("fuel exhausted");
|
||||
}
|
||||
remaining > 0
|
||||
})
|
||||
}
|
||||
|
|
|
@ -142,7 +142,7 @@ impl ToChalk for Ty {
|
|||
let substitution = proj_ty.parameters.to_chalk(db);
|
||||
chalk_ir::AliasTy { associated_ty_id, substitution }.cast().intern()
|
||||
}
|
||||
Ty::Param(id) => {
|
||||
Ty::Placeholder(id) => {
|
||||
let interned_id = db.intern_type_param_id(id);
|
||||
PlaceholderIndex {
|
||||
ui: UniverseIndex::ROOT,
|
||||
|
@ -184,7 +184,7 @@ impl ToChalk for Ty {
|
|||
let interned_id = crate::db::GlobalTypeParamId::from_intern_id(
|
||||
crate::salsa::InternId::from(idx.idx),
|
||||
);
|
||||
Ty::Param(db.lookup_intern_type_param_id(interned_id))
|
||||
Ty::Placeholder(db.lookup_intern_type_param_id(interned_id))
|
||||
}
|
||||
chalk_ir::TyData::Alias(proj) => {
|
||||
let associated_ty = from_chalk(db, proj.associated_ty_id);
|
||||
|
|
|
@ -16,6 +16,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
|||
.literal { color: #BFEBBF; }
|
||||
.literal\.numeric { color: #6A8759; }
|
||||
.macro { color: #94BFF3; }
|
||||
.module { color: #AFD8AF; }
|
||||
.variable { color: #DCDCCC; }
|
||||
.variable\.mut { color: #DCDCCC; text-decoration: underline; }
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
|||
.literal { color: #BFEBBF; }
|
||||
.literal\.numeric { color: #6A8759; }
|
||||
.macro { color: #94BFF3; }
|
||||
.module { color: #AFD8AF; }
|
||||
.variable { color: #DCDCCC; }
|
||||
.variable\.mut { color: #DCDCCC; text-decoration: underline; }
|
||||
|
||||
|
|
|
@ -365,6 +365,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
|||
.literal { color: #BFEBBF; }
|
||||
.literal\\.numeric { color: #6A8759; }
|
||||
.macro { color: #94BFF3; }
|
||||
.module { color: #AFD8AF; }
|
||||
.variable { color: #DCDCCC; }
|
||||
.variable\\.mut { color: #DCDCCC; text-decoration: underline; }
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ fst = { version = "0.3.1", default-features = false }
|
|||
rustc-hash = "1.0"
|
||||
unicase = "2.2.0"
|
||||
superslice = "1.0.0"
|
||||
rand = { version = "0.7.0", features = ["small_rng"] }
|
||||
once_cell = "1.2.0"
|
||||
|
||||
ra_syntax = { path = "../ra_syntax" }
|
||||
|
|
|
@ -15,13 +15,8 @@ fn main() -> Result<()> {
|
|||
|
||||
fn setup_logging() -> Result<()> {
|
||||
std::env::set_var("RUST_BACKTRACE", "short");
|
||||
|
||||
env_logger::try_init()?;
|
||||
|
||||
ra_prof::set_filter(match std::env::var("RA_PROFILE") {
|
||||
Ok(spec) => ra_prof::Filter::from_spec(&spec),
|
||||
Err(_) => ra_prof::Filter::disabled(),
|
||||
});
|
||||
ra_prof::init();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,13 @@ pub use crate::memory_usage::{Bytes, MemoryUsage};
|
|||
#[global_allocator]
|
||||
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
||||
|
||||
pub fn init() {
|
||||
set_filter(match std::env::var("RA_PROFILE") {
|
||||
Ok(spec) => Filter::from_spec(&spec),
|
||||
Err(_) => Filter::disabled(),
|
||||
});
|
||||
}
|
||||
|
||||
/// Set profiling filter. It specifies descriptions allowed to profile.
|
||||
/// This is helpful when call stack has too many nested profiling scopes.
|
||||
/// Additionally filter can specify maximum depth of profiling scopes nesting.
|
||||
|
|
|
@ -418,8 +418,10 @@ pub fn get_rustc_cfg_options() -> CfgOptions {
|
|||
// Some nightly-only cfgs, which are required for stdlib
|
||||
{
|
||||
cfg_options.insert_atom("target_thread_local".into());
|
||||
for &target_has_atomic in ["16", "32", "64", "8", "cas", "ptr"].iter() {
|
||||
cfg_options.insert_key_value("target_has_atomic".into(), target_has_atomic.into())
|
||||
for &target_has_atomic in ["8", "16", "32", "64", "cas", "ptr"].iter() {
|
||||
cfg_options.insert_key_value("target_has_atomic".into(), target_has_atomic.into());
|
||||
cfg_options
|
||||
.insert_key_value("target_has_atomic_load_store".into(), target_has_atomic.into());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,280 +0,0 @@
|
|||
[github-releases]: https://github.com/rust-analyzer/rust-analyzer/releases
|
||||
|
||||
The main interface to rust-analyzer is the
|
||||
[LSP](https://microsoft.github.io/language-server-protocol/) implementation. To
|
||||
install lsp server, you have three options:
|
||||
|
||||
* **Preferred and default:** install the plugin/extension for your IDE and it will ask your permission to automatically download the latest lsp server for you from [GitHub releases][github-releases]. (See docs to find out whether this is implemented for your editor below).
|
||||
* Manually download prebuilt binaries from [GitHub releases][github-releases]
|
||||
* `ra_lsp_server-linux` for Linux
|
||||
* `ra_lsp_server-mac` for Mac
|
||||
* `ra_lsp_server-windows.exe` for Windows
|
||||
* Clone the repository and build from sources
|
||||
```bash
|
||||
$ git clone git@github.com:rust-analyzer/rust-analyzer && cd rust-analyzer
|
||||
$ cargo xtask install --server # or cargo install --path ./crates/ra_lsp_server
|
||||
```
|
||||
|
||||
This way you will get a binary named `ra_lsp_server` (with os suffix for prebuilt binaries)
|
||||
which you should be able to use with any LSP-compatible editor.
|
||||
|
||||
We make use of custom extensions to LSP, so special client-side support is required to take full
|
||||
advantage of rust-analyzer. This repository contains support code for VS Code.
|
||||
|
||||
Rust Analyzer needs sources of rust standard library to work, so
|
||||
you might also need to execute
|
||||
|
||||
```
|
||||
$ rustup component add rust-src
|
||||
```
|
||||
|
||||
See [./features.md](./features.md) document for a list of features that are available.
|
||||
|
||||
## VS Code
|
||||
|
||||
### Prerequisites
|
||||
|
||||
You will need the most recent version of VS Code: we don't try to
|
||||
maintain compatibility with older versions yet.
|
||||
|
||||
### Installation from prebuilt binaries
|
||||
|
||||
We ship prebuilt binaries for Linux, Mac and Windows via
|
||||
[GitHub releases][github-releases].
|
||||
In order to use them you need to install the client VSCode extension.
|
||||
|
||||
Publishing to VS Code marketplace is currently WIP. Thus, you need to manually download
|
||||
`rust-analyzer-0.1.0.vsix` file from latest [GitHub release][github-releases].
|
||||
|
||||
After you downloaded the `.vsix` file you can install it from the terminal
|
||||
|
||||
```
|
||||
$ code --install-extension rust-analyzer-0.1.0.vsix
|
||||
```
|
||||
|
||||
Or open VS Code, press <kbd>Ctrl+Shift+P</kbd>, and search for the following command:
|
||||
|
||||
<img width="500px" alt="Install from VSIX command" src="https://user-images.githubusercontent.com/36276403/74108225-c0c11d80-4b80-11ea-9b2a-0a43f09e29af.png">
|
||||
|
||||
Press <kbd>Enter</kbd> and go to `rust-analyzer-0.1.0.vsix` file through the file explorer.
|
||||
|
||||
Then open some Rust project and you should
|
||||
see an info message pop-up.
|
||||
|
||||
<img height="140px" src="https://user-images.githubusercontent.com/36276403/74103174-a40df100-4b52-11ea-81f4-372c70797924.png" alt="Download now message"/>
|
||||
|
||||
|
||||
Click `Download now`, wait until the progress is 100% and you are ready to go.
|
||||
|
||||
For updates you need to remove installed binary
|
||||
```
|
||||
rm -rf ${HOME}/.config/Code/User/globalStorage/matklad.rust-analyzer
|
||||
```
|
||||
|
||||
`"Download latest language server"` command for VSCode and automatic updates detection is currently WIP.
|
||||
|
||||
|
||||
### Installation from sources
|
||||
|
||||
In order to build the VS Code plugin from sources, you need to have node.js and npm with
|
||||
a minimum version of 12 installed. Please refer to
|
||||
[node.js and npm documentation](https://nodejs.org) for installation instructions.
|
||||
|
||||
The experimental VS Code plugin can be built and installed by executing the
|
||||
following commands:
|
||||
|
||||
```
|
||||
$ git clone https://github.com/rust-analyzer/rust-analyzer.git --depth 1
|
||||
$ cd rust-analyzer
|
||||
$ cargo xtask install
|
||||
```
|
||||
|
||||
After that you need to amend your `settings.json` file to explicitly specify the
|
||||
path to `ra_lsp_server` that you've just built.
|
||||
```json
|
||||
{
|
||||
"rust-analyzer.raLspServerPath": "ra_lsp_server"
|
||||
}
|
||||
```
|
||||
This should work on all platforms, otherwise if installed `ra_lsp_server` is not available through your `$PATH` then see how to configure it [here](#setting-up-the-PATH-variable).
|
||||
|
||||
|
||||
The automatic installation is expected to *just work* for common cases, if it
|
||||
doesn't, report bugs!
|
||||
|
||||
**Note** [#1831](https://github.com/rust-analyzer/rust-analyzer/issues/1831): If you are using the popular
|
||||
[Vim emulation plugin](https://github.com/VSCodeVim/Vim), you will likely
|
||||
need to turn off the `rust-analyzer.enableEnhancedTyping` setting.
|
||||
(// TODO: This configuration is no longer available, enhanced typing shoud be disabled via removing Enter key binding, [see this issue](https://github.com/rust-analyzer/rust-analyzer/issues/3051))
|
||||
|
||||
If you have an unusual setup (for example, `code` is not in the `PATH`), you
|
||||
should adapt these manual installation instructions:
|
||||
|
||||
```
|
||||
$ git clone https://github.com/rust-analyzer/rust-analyzer.git --depth 1
|
||||
$ cd rust-analyzer
|
||||
$ cargo install --path ./crates/ra_lsp_server/ --force --locked
|
||||
$ cd ./editors/code
|
||||
$ npm install
|
||||
$ npm run package
|
||||
$ code --install-extension ./rust-analyzer-0.1.0.vsix
|
||||
```
|
||||
|
||||
It's better to remove existing Rust plugins to avoid interference.
|
||||
|
||||
Beyond basic LSP features, there are some extension commands which you can
|
||||
invoke via <kbd>Ctrl+Shift+P</kbd> or bind to a shortcut. See [./features.md](./features.md)
|
||||
for details.
|
||||
|
||||
For updates, pull the latest changes from the master branch, run `cargo xtask install` again, and **restart** VS Code instance.
|
||||
See [microsoft/vscode#72308](https://github.com/microsoft/vscode/issues/72308) for why a full restart is needed.
|
||||
|
||||
### VS Code Remote
|
||||
|
||||
You can also use `rust-analyzer` with the Visual Studio Code Remote extensions
|
||||
(Remote SSH, Remote WSL, Remote Containers). In this case, however, you have to
|
||||
manually install the `.vsix` package:
|
||||
|
||||
1. Build the extension on the remote host using the instructions above (ignore the
|
||||
error if `code` cannot be found in your PATH: VSCode doesn't need to be installed
|
||||
on the remote host).
|
||||
2. In Visual Studio Code open a connection to the remote host.
|
||||
3. Open the Extensions View (`View > Extensions`, keyboard shortcut: `Ctrl+Shift+X`).
|
||||
4. From the top-right kebab menu (`···`) select `Install from VSIX...`
|
||||
5. Inside the `rust-analyzer` directory find the `editors/code` subdirectory and choose
|
||||
the `rust-analyzer-0.1.0.vsix` file.
|
||||
6. Restart Visual Studio Code and re-establish the connection to the remote host.
|
||||
|
||||
In case of errors please make sure that `~/.cargo/bin` is in your `PATH` on the remote
|
||||
host.
|
||||
|
||||
### Settings
|
||||
|
||||
* `rust-analyzer.highlightingOn`: enables experimental syntax highlighting.
|
||||
Colors can be configured via `editor.tokenColorCustomizations`.
|
||||
As an example, [Pale Fire](https://github.com/matklad/pale-fire/) color scheme tweaks rust colors.
|
||||
* `rust-analyzer.enableEnhancedTyping`: by default, rust-analyzer intercepts the
|
||||
`Enter` key to make it easier to continue comments. Note that it may conflict with VIM emulation plugin.
|
||||
* `rust-analyzer.raLspServerPath`: path to `ra_lsp_server` executable, when absent or `null` defaults to prebuilt binary path
|
||||
* `rust-analyzer.enableCargoWatchOnStartup`: prompt to install & enable `cargo
|
||||
watch` for live error highlighting (note, this **does not** use rust-analyzer)
|
||||
* `rust-analyzer.excludeGlobs`: a list of glob-patterns for exclusion (see globset [docs](https://docs.rs/globset) for syntax).
|
||||
Note: glob patterns are applied to all Cargo packages and a rooted at a package root.
|
||||
This is not very intuitive and a limitation of a current implementation.
|
||||
* `rust-analyzer.useClientWatching`: use client provided file watching instead
|
||||
of notify watching.
|
||||
* `rust-analyzer.cargo-watch.command`: `cargo-watch` command. (e.g: `clippy` will run as `cargo watch -x clippy` )
|
||||
* `rust-analyzer.cargo-watch.arguments`: cargo-watch check arguments.
|
||||
(e.g: `--features="shumway,pdf"` will run as `cargo watch -x "check --features="shumway,pdf""` )
|
||||
* `rust-analyzer.cargo-watch.ignore`: list of patterns for cargo-watch to ignore (will be passed as `--ignore`)
|
||||
* `rust-analyzer.trace.server`: enables internal logging
|
||||
* `rust-analyzer.trace.cargo-watch`: enables cargo-watch logging
|
||||
* `RUST_SRC_PATH`: environment variable that overwrites the sysroot
|
||||
* `rust-analyzer.featureFlags` -- a JSON object to tweak fine-grained behavior:
|
||||
```jsonc
|
||||
{
|
||||
// Show diagnostics produced by rust-analyzer itself.
|
||||
"lsp.diagnostics": true,
|
||||
// Automatically insert `()` and `<>` when completing functions and types.
|
||||
"completion.insertion.add-call-parenthesis": true,
|
||||
// Enable completions like `.if`, `.match`, etc.
|
||||
"completion.enable-postfix": true,
|
||||
// Show notification when workspace is fully loaded
|
||||
"notifications.workspace-loaded": true,
|
||||
// Show error when no Cargo.toml was found
|
||||
"notifications.cargo-toml-not-found": true,
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Emacs
|
||||
|
||||
* install recent version of `emacs-lsp` package by following the instructions [here][emacs-lsp]
|
||||
* set `lsp-rust-server` to `'rust-analyzer`
|
||||
* run `lsp` in a Rust buffer
|
||||
* (Optionally) bind commands like `lsp-rust-analyzer-join-lines`, `lsp-extend-selection` and `lsp-rust-analyzer-expand-macro` to keys
|
||||
|
||||
[emacs-lsp]: https://github.com/emacs-lsp/lsp-mode
|
||||
|
||||
|
||||
## Vim and NeoVim (coc-rust-analyzer)
|
||||
|
||||
* Install coc.nvim by following the instructions at [coc.nvim][] (nodejs required)
|
||||
* Run `:CocInstall coc-rust-analyzer` to install [coc-rust-analyzer], this extension implements _most_ of the features supported in the VSCode extension:
|
||||
- same configurations as VSCode extension, `rust-analyzer.raLspServerPath`, `rust-analyzer.enableCargoWatchOnStartup` etc.
|
||||
- same commands too, `rust-analyzer.analyzerStatus`, `rust-analyzer.startCargoWatch` etc.
|
||||
- highlighting and inlay_hints are not implemented yet
|
||||
|
||||
[coc.nvim]: https://github.com/neoclide/coc.nvim
|
||||
[coc-rust-analyzer]: https://github.com/fannheyward/coc-rust-analyzer
|
||||
|
||||
## Vim and NeoVim (LanguageClient-neovim)
|
||||
|
||||
* Install LanguageClient-neovim by following the instructions [here][lang-client-neovim]
|
||||
- The github project wiki has extra tips on configuration
|
||||
|
||||
* Configure by adding this to your vim/neovim config file (replacing the existing rust specific line if it exists):
|
||||
|
||||
```vim
|
||||
let g:LanguageClient_serverCommands = {
|
||||
\ 'rust': ['ra_lsp_server'],
|
||||
\ }
|
||||
```
|
||||
|
||||
[lang-client-neovim]: https://github.com/autozimu/LanguageClient-neovim
|
||||
|
||||
## NeoVim (nvim-lsp)
|
||||
|
||||
NeoVim 0.5 (not yet released) has built in language server support. For a quick start configuration
|
||||
of rust-analyzer, use [neovim/nvim-lsp](https://github.com/neovim/nvim-lsp#rust_analyzer).
|
||||
Once `neovim/nvim-lsp` is installed, use `lua require'nvim_lsp'.rust_analyzer.setup({})` in your `init.vim`.
|
||||
|
||||
|
||||
## Sublime Text 3
|
||||
|
||||
Prequisites:
|
||||
|
||||
`LSP` package.
|
||||
|
||||
Installation:
|
||||
|
||||
* Invoke the command palette with <kbd>Ctrl+Shift+P</kbd>
|
||||
* Type `LSP Settings` to open the LSP preferences editor
|
||||
* Add the following LSP client definition to your settings:
|
||||
|
||||
```json
|
||||
"rust-analyzer": {
|
||||
"command": ["ra_lsp_server"],
|
||||
"languageId": "rust",
|
||||
"scopes": ["source.rust"],
|
||||
"syntaxes": [
|
||||
"Packages/Rust/Rust.sublime-syntax",
|
||||
"Packages/Rust Enhanced/RustEnhanced.sublime-syntax"
|
||||
],
|
||||
"initializationOptions": {
|
||||
"featureFlags": {
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
* You can now invoke the command palette and type LSP enable to locally/globally enable the rust-analyzer LSP (type LSP enable, then choose either locally or globally, then select rust-analyzer)
|
||||
|
||||
|
||||
<!-- Update links to this header when changing it! -->
|
||||
### Setting up the `PATH` variable
|
||||
|
||||
On Unix systems, `rustup` adds `~/.cargo/bin` to `PATH` by modifying the shell's
|
||||
startup file. Depending on your configuration, your Desktop Environment might not
|
||||
actually load it. If you find that `rust-analyzer` only runs when starting the
|
||||
editor from the terminal, you will have to set up your `PATH` variable manually.
|
||||
|
||||
There are a couple of ways to do that:
|
||||
|
||||
- for Code, set `rust-analyzer.raLspServerPath` to `~/.cargo/bin` (the `~` is
|
||||
automatically resolved by the extension)
|
||||
- copy the binary to a location that is already in `PATH`, e.g. `/usr/local/bin`
|
||||
- on Linux, use PAM to configure the `PATH` variable, by e.g. putting
|
||||
`PATH DEFAULT=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:@{HOME}/.cargo/bin:@{HOME}/.local/bin`
|
||||
in your `~/.pam_environment` file; note that this might interfere with other
|
||||
defaults set by the system administrator via `/etc/environment`.
|
154
docs/user/readme.adoc
Normal file
154
docs/user/readme.adoc
Normal file
|
@ -0,0 +1,154 @@
|
|||
= User Manual
|
||||
:toc: preamble
|
||||
:sectanchors:
|
||||
:page-layout: post
|
||||
|
||||
|
||||
// Master copy of this document lives in the https://github.com/rust-analyzer/rust-analyzer repository
|
||||
|
||||
At it's core, rust-analyzer is a *library* for semantic analysis of the Rust code as it changes over time.
|
||||
This manual focuses on a specific usage of the library -- the implementation of
|
||||
https://microsoft.github.io/language-server-protocol/[Language Server Protocol].
|
||||
LSP allows various code editors, like VS Code, Emacs or Vim, to implement semantic feature like completion or goto definition by talking to an external language server process.
|
||||
|
||||
To improve this document, send a pull request against
|
||||
https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/user/readme.adoc[this file].
|
||||
|
||||
== Installation
|
||||
|
||||
In theory, one should be able to just install the server binary and have it automatically work with any editor.
|
||||
We are not there yet, so some editor specific setup is required.
|
||||
|
||||
=== VS Code
|
||||
|
||||
This the best supported editor at the moment.
|
||||
rust-analyzer plugin for VS Code is maintained
|
||||
https://github.com/rust-analyzer/rust-analyzer/tree/master/editors/code[in tree].
|
||||
|
||||
You can install the latest release of the plugin from
|
||||
https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer[the marketplace].
|
||||
By default, the plugin will download the matching version of the server as well.
|
||||
|
||||
// FIXME: update the image (its text has changed)
|
||||
image::https://user-images.githubusercontent.com/36276403/74103174-a40df100-4b52-11ea-81f4-372c70797924.png[]
|
||||
|
||||
The server binary is stored in `~/.config/Code/User/globalStorage/matklad.rust-analyzer`.
|
||||
|
||||
Note that we only support the latest version of VS Code.
|
||||
|
||||
==== Updates
|
||||
|
||||
The extension will be updated automatically as new versions become available. It will ask your permission to download the matching language server version binary if needed.
|
||||
|
||||
==== Building From Source
|
||||
|
||||
Alternatively, both the server and the plugin can be installed from source:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ git clone https://github.com/rust-analyzer/rust-analyzer.git && cd rust-analyzer
|
||||
$ cargo xtask install
|
||||
----
|
||||
|
||||
You'll need Cargo, nodejs and npm for this.
|
||||
To make VS Code use the freshly build server, add this to the settings:
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{ "rust-analyzer.raLspServerPath": "ra_lsp_server" }
|
||||
----
|
||||
|
||||
Note that installing via `xtask install` does not work for VS Code Remote, instead you'll need to install the `.vsix` manually.
|
||||
|
||||
=== Language Server Binary
|
||||
|
||||
Other editors generally require `ra_lsp_server` binary to be in `$PATH`.
|
||||
You can download pre-build binary from
|
||||
https://github.com/rust-analyzer/rust-analyzer/releases[relases]
|
||||
page, or you can install it from source using the following command:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
$ cargo xtask install --server
|
||||
----
|
||||
|
||||
=== Emacs
|
||||
|
||||
Emacs support is maintained https://github.com/emacs-lsp/lsp-mode/blob/master/lsp-rust.el[upstream].
|
||||
|
||||
1. Install recent version of `emacs-lsp` package by following the instructions https://github.com/emacs-lsp/lsp-mode[here].
|
||||
2. Set `lsp-rust-server` to `'rust-analyzer`.
|
||||
3. Run `lsp` in a Rust buffer.
|
||||
4. (Optionally) bind commands like `lsp-rust-analyzer-join-lines`, `lsp-extend-selection` and `lsp-rust-analyzer-expand-macro` to keys.
|
||||
|
||||
=== Vim
|
||||
|
||||
The are several LSP client implementations for vim:
|
||||
|
||||
==== coc-rust-analyzer
|
||||
|
||||
1. Install coc.nvim by following the instructions at
|
||||
https://github.com/neoclide/coc.nvim[coc.nvim]
|
||||
(nodejs required)
|
||||
2. Run `:CocInstall coc-rust-analyzer` to install
|
||||
https://github.com/fannheyward/coc-rust-analyzer[coc-rust-analyzer],
|
||||
this extension implements _most_ of the features supported in the VSCode extension:
|
||||
* same configurations as VSCode extension, `rust-analyzer.raLspServerPath`, `rust-analyzer.enableCargoWatchOnStartup` etc.
|
||||
* same commands too, `rust-analyzer.analyzerStatus`, `rust-analyzer.startCargoWatch` etc.
|
||||
* highlighting and inlay_hints are not implemented yet
|
||||
|
||||
==== LanguageClient-neovim
|
||||
|
||||
1. Install LanguageClient-neovim by following the instructions
|
||||
https://github.com/autozimu/LanguageClient-neovim[here]
|
||||
* The github project wiki has extra tips on configuration
|
||||
|
||||
2. Configure by adding this to your vim/neovim config file (replacing the existing rust specific line if it exists):
|
||||
+
|
||||
[source,vim]
|
||||
----
|
||||
let g:LanguageClient_serverCommands = {
|
||||
\ 'rust': ['ra_lsp_server'],
|
||||
\ }
|
||||
----
|
||||
|
||||
==== nvim-lsp
|
||||
|
||||
NeoVim 0.5 (not yet released) has built in language server support.
|
||||
For a quick start configuration of rust-analyzer, use https://github.com/neovim/nvim-lsp#rust_analyzer[neovim/nvim-lsp].
|
||||
Once `neovim/nvim-lsp` is installed, use `lua require'nvim_lsp'.rust_analyzer.setup({})` in your `init.vim`.
|
||||
|
||||
=== Sublime Text 3
|
||||
|
||||
Prerequisites:
|
||||
|
||||
`LSP` package.
|
||||
|
||||
Installation:
|
||||
|
||||
1. Invoke the command palette with <kbd>Ctrl+Shift+P</kbd>
|
||||
2. Type `LSP Settings` to open the LSP preferences editor
|
||||
3. Add the following LSP client definition to your settings:
|
||||
+
|
||||
[source,json]
|
||||
----
|
||||
"rust-analyzer": {
|
||||
"command": ["ra_lsp_server"],
|
||||
"languageId": "rust",
|
||||
"scopes": ["source.rust"],
|
||||
"syntaxes": [
|
||||
"Packages/Rust/Rust.sublime-syntax",
|
||||
"Packages/Rust Enhanced/RustEnhanced.sublime-syntax"
|
||||
],
|
||||
"initializationOptions": {
|
||||
"featureFlags": {
|
||||
}
|
||||
},
|
||||
}
|
||||
----
|
||||
|
||||
4. You can now invoke the command palette and type LSP enable to locally/globally enable the rust-analyzer LSP (type LSP enable, then choose either locally or globally, then select rust-analyzer)
|
||||
|
||||
== Usage
|
||||
|
||||
See https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/user/features.md[features.md].
|
20
editors/code/package-lock.json
generated
20
editors/code/package-lock.json
generated
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "rust-analyzer",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0-dev",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -107,9 +107,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"@types/vscode": {
|
||||
"version": "1.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.41.0.tgz",
|
||||
"integrity": "sha512-7SfeY5u9jgiELwxyLB3z7l6l/GbN9CqpCQGkcRlB7tKRFBxzbz2PoBfGrLxI1vRfUCIq5+hg5vtDHExwq5j3+A==",
|
||||
"version": "1.42.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.42.0.tgz",
|
||||
"integrity": "sha512-ds6TceMsh77Fs0Mq0Vap6Y72JbGWB8Bay4DrnJlf5d9ui2RSe1wis13oQm+XhguOeH1HUfLGzaDAoupTUtgabw==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn": {
|
||||
|
@ -662,9 +662,9 @@
|
|||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
|
||||
"integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
|
@ -860,9 +860,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"vsce": {
|
||||
"version": "1.71.0",
|
||||
"resolved": "https://registry.npmjs.org/vsce/-/vsce-1.71.0.tgz",
|
||||
"integrity": "sha512-7k+LPC4oJYPyyxs0a5nh4A8CleQ6+2EMPiAiX/bDyN+PmwJFm2FFPqLRxdIsIWfFnkW4ZMQBf10+W62dCRd9kQ==",
|
||||
"version": "1.73.0",
|
||||
"resolved": "https://registry.npmjs.org/vsce/-/vsce-1.73.0.tgz",
|
||||
"integrity": "sha512-6W37Ebbkj3uF3WhT+SCfRtsneRQEFcGvf/XYz+b6OAgDCj4gPurWyDVrqw/HLsbP1WflGIyUfVZ8t5M7kQp6Uw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"azure-devops-node-api": "^7.2.0",
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
"preview": true,
|
||||
"private": true,
|
||||
"icon": "icon.png",
|
||||
"version": "0.1.0",
|
||||
"//": "The real version is in release.yaml, this one just needs to be bigger",
|
||||
"version": "0.2.20200211-dev",
|
||||
"publisher": "matklad",
|
||||
"repository": {
|
||||
"url": "https://github.com/rust-analyzer/rust-analyzer.git",
|
||||
|
@ -15,7 +16,7 @@
|
|||
"Other"
|
||||
],
|
||||
"engines": {
|
||||
"vscode": "^1.41.0"
|
||||
"vscode": "^1.42.0"
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "tsc && rollup -c",
|
||||
|
@ -35,13 +36,13 @@
|
|||
"@types/node": "^12.12.25",
|
||||
"@types/node-fetch": "^2.5.4",
|
||||
"@types/throttle-debounce": "^2.1.0",
|
||||
"@types/vscode": "^1.41.0",
|
||||
"@types/vscode": "^1.42.0",
|
||||
"rollup": "^1.31.0",
|
||||
"tslib": "^1.10.0",
|
||||
"tslint": "^5.20.1",
|
||||
"typescript": "^3.7.5",
|
||||
"typescript-formatter": "^7.2.2",
|
||||
"vsce": "^1.71.0"
|
||||
"vsce": "^1.73.0"
|
||||
},
|
||||
"activationEvents": [
|
||||
"onLanguage:rust",
|
||||
|
@ -181,6 +182,9 @@
|
|||
},
|
||||
"rust-analyzer.excludeGlobs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [],
|
||||
"description": "Paths to exclude from analysis"
|
||||
},
|
||||
|
@ -196,6 +200,9 @@
|
|||
},
|
||||
"rust-analyzer.cargo-watch.arguments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "`cargo-watch` arguments. (e.g: `--features=\"shumway,pdf\"` will run as `cargo watch -x \"check --features=\"shumway,pdf\"\"` )",
|
||||
"default": []
|
||||
},
|
||||
|
@ -226,11 +233,10 @@
|
|||
"description": "Trace requests to the ra_lsp_server"
|
||||
},
|
||||
"rust-analyzer.lruCapacity": {
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
],
|
||||
"type": [ "null", "integer" ],
|
||||
"default": null,
|
||||
"minimum": 0,
|
||||
"exclusiveMinimum": true,
|
||||
"description": "Number of syntax trees rust-analyzer keeps in memory"
|
||||
},
|
||||
"rust-analyzer.displayInlayHints": {
|
||||
|
@ -239,8 +245,10 @@
|
|||
"description": "Display additional type and parameter information in the editor"
|
||||
},
|
||||
"rust-analyzer.maxInlayHintLength": {
|
||||
"type": "number",
|
||||
"type": [ "null", "integer" ],
|
||||
"default": 20,
|
||||
"minimum": 0,
|
||||
"exclusiveMinimum": true,
|
||||
"description": "Maximum length for inlay hints"
|
||||
},
|
||||
"rust-analyzer.cargoFeatures.noDefaultFeatures": {
|
||||
|
@ -255,6 +263,9 @@
|
|||
},
|
||||
"rust-analyzer.cargoFeatures.features": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [],
|
||||
"description": "List of features to activate"
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ export default {
|
|||
external: [...nodeBuiltins, 'vscode'],
|
||||
output: {
|
||||
file: './out/main.js',
|
||||
format: 'cjs'
|
||||
format: 'cjs',
|
||||
exports: 'named'
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,39 +1,42 @@
|
|||
import * as lc from 'vscode-languageclient';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { window, workspace } from 'vscode';
|
||||
import { Config } from './config';
|
||||
import { ensureLanguageServerBinary } from './installation/language_server';
|
||||
import { ensureServerBinary } from './installation/server';
|
||||
import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed';
|
||||
|
||||
export async function createClient(config: Config): Promise<null | lc.LanguageClient> {
|
||||
// '.' Is the fallback if no folder is open
|
||||
// TODO?: Workspace folders support Uri's (eg: file://test.txt).
|
||||
// It might be a good idea to test if the uri points to a file.
|
||||
const workspaceFolderPath = workspace.workspaceFolders?.[0]?.uri.fsPath ?? '.';
|
||||
const workspaceFolderPath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? '.';
|
||||
|
||||
const raLspServerPath = await ensureLanguageServerBinary(config.langServerSource);
|
||||
if (!raLspServerPath) return null;
|
||||
const serverPath = await ensureServerBinary(config.serverSource);
|
||||
if (!serverPath) return null;
|
||||
|
||||
const run: lc.Executable = {
|
||||
command: raLspServerPath,
|
||||
command: serverPath,
|
||||
options: { cwd: workspaceFolderPath },
|
||||
};
|
||||
const serverOptions: lc.ServerOptions = {
|
||||
run,
|
||||
debug: run,
|
||||
};
|
||||
const traceOutputChannel = window.createOutputChannel(
|
||||
const traceOutputChannel = vscode.window.createOutputChannel(
|
||||
'Rust Analyzer Language Server Trace',
|
||||
);
|
||||
const cargoWatchOpts = config.cargoWatchOptions;
|
||||
|
||||
const clientOptions: lc.LanguageClientOptions = {
|
||||
documentSelector: [{ scheme: 'file', language: 'rust' }],
|
||||
initializationOptions: {
|
||||
publishDecorations: true,
|
||||
lruCapacity: config.lruCapacity,
|
||||
maxInlayHintLength: config.maxInlayHintLength,
|
||||
cargoWatchEnable: config.cargoWatchOptions.enable,
|
||||
cargoWatchArgs: config.cargoWatchOptions.arguments,
|
||||
cargoWatchCommand: config.cargoWatchOptions.command,
|
||||
cargoWatchAllTargets: config.cargoWatchOptions.allTargets,
|
||||
cargoWatchEnable: cargoWatchOpts.enable,
|
||||
cargoWatchArgs: cargoWatchOpts.arguments,
|
||||
cargoWatchCommand: cargoWatchOpts.command,
|
||||
cargoWatchAllTargets: cargoWatchOpts.allTargets,
|
||||
excludeGlobs: config.excludeGlobs,
|
||||
useClientWatching: config.useClientWatching,
|
||||
featureFlags: config.featureFlags,
|
||||
|
@ -78,6 +81,10 @@ export async function createClient(config: Config): Promise<null | lc.LanguageCl
|
|||
}
|
||||
},
|
||||
};
|
||||
res.registerProposedFeatures();
|
||||
|
||||
// To turn on all proposed features use: res.registerProposedFeatures();
|
||||
// Here we want to just enable CallHierarchyFeature since it is available on stable.
|
||||
// Note that while the CallHierarchyFeature is stable the LSP protocol is not.
|
||||
res.registerFeature(new CallHierarchyFeature(res));
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -16,45 +16,62 @@ export interface CargoFeatures {
|
|||
allFeatures: boolean;
|
||||
features: string[];
|
||||
}
|
||||
|
||||
export class Config {
|
||||
langServerSource!: null | BinarySource;
|
||||
private static readonly rootSection = "rust-analyzer";
|
||||
private static readonly requiresReloadOpts = [
|
||||
"cargoFeatures",
|
||||
"cargo-watch",
|
||||
]
|
||||
.map(opt => `${Config.rootSection}.${opt}`);
|
||||
|
||||
highlightingOn = true;
|
||||
rainbowHighlightingOn = false;
|
||||
enableEnhancedTyping = true;
|
||||
lruCapacity: null | number = null;
|
||||
displayInlayHints = true;
|
||||
maxInlayHintLength: null | number = null;
|
||||
excludeGlobs: string[] = [];
|
||||
useClientWatching = true;
|
||||
featureFlags: Record<string, boolean> = {};
|
||||
// for internal use
|
||||
withSysroot: null | boolean = null;
|
||||
cargoWatchOptions: CargoWatchOptions = {
|
||||
enable: true,
|
||||
arguments: [],
|
||||
command: '',
|
||||
allTargets: true,
|
||||
};
|
||||
cargoFeatures: CargoFeatures = {
|
||||
noDefaultFeatures: false,
|
||||
allFeatures: true,
|
||||
features: [],
|
||||
};
|
||||
private static readonly extensionVersion: string = (() => {
|
||||
const packageJsonVersion = vscode
|
||||
.extensions
|
||||
.getExtension("matklad.rust-analyzer")!
|
||||
.packageJSON
|
||||
.version as string; // n.n.YYYYMMDD
|
||||
|
||||
private prevEnhancedTyping: null | boolean = null;
|
||||
private prevCargoFeatures: null | CargoFeatures = null;
|
||||
private prevCargoWatchOptions: null | CargoWatchOptions = null;
|
||||
const realVersionRegexp = /^\d+\.\d+\.(\d{4})(\d{2})(\d{2})/;
|
||||
const [, yyyy, mm, dd] = packageJsonVersion.match(realVersionRegexp)!;
|
||||
|
||||
constructor(ctx: vscode.ExtensionContext) {
|
||||
vscode.workspace.onDidChangeConfiguration(_ => this.refresh(ctx), null, ctx.subscriptions);
|
||||
this.refresh(ctx);
|
||||
return `${yyyy}-${mm}-${dd}`;
|
||||
})();
|
||||
|
||||
private cfg!: vscode.WorkspaceConfiguration;
|
||||
|
||||
constructor(private readonly ctx: vscode.ExtensionContext) {
|
||||
vscode.workspace.onDidChangeConfiguration(this.onConfigChange, this, ctx.subscriptions);
|
||||
this.refreshConfig();
|
||||
}
|
||||
|
||||
private static expandPathResolving(path: string) {
|
||||
if (path.startsWith('~/')) {
|
||||
return path.replace('~', os.homedir());
|
||||
|
||||
private refreshConfig() {
|
||||
this.cfg = vscode.workspace.getConfiguration(Config.rootSection);
|
||||
console.log("Using configuration:", this.cfg);
|
||||
}
|
||||
|
||||
private async onConfigChange(event: vscode.ConfigurationChangeEvent) {
|
||||
this.refreshConfig();
|
||||
|
||||
const requiresReloadOpt = Config.requiresReloadOpts.find(
|
||||
opt => event.affectsConfiguration(opt)
|
||||
);
|
||||
|
||||
if (!requiresReloadOpt) return;
|
||||
|
||||
const userResponse = await vscode.window.showInformationMessage(
|
||||
`Changing "${requiresReloadOpt}" requires a reload`,
|
||||
"Reload now"
|
||||
);
|
||||
|
||||
if (userResponse === "Reload now") {
|
||||
vscode.commands.executeCommand("workbench.action.reloadWindow");
|
||||
}
|
||||
}
|
||||
|
||||
private static replaceTildeWithHomeDir(path: string) {
|
||||
if (path.startsWith("~/")) {
|
||||
return os.homedir() + path.slice("~".length);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
@ -64,17 +81,14 @@ export class Config {
|
|||
* `platform` on GitHub releases. (It is also stored under the same name when
|
||||
* downloaded by the extension).
|
||||
*/
|
||||
private static prebuiltLangServerFileName(
|
||||
platform: NodeJS.Platform,
|
||||
arch: string
|
||||
): null | string {
|
||||
get prebuiltServerFileName(): null | string {
|
||||
// See possible `arch` values here:
|
||||
// https://nodejs.org/api/process.html#process_process_arch
|
||||
|
||||
switch (platform) {
|
||||
switch (process.platform) {
|
||||
|
||||
case "linux": {
|
||||
switch (arch) {
|
||||
switch (process.arch) {
|
||||
case "arm":
|
||||
case "arm64": return null;
|
||||
|
||||
|
@ -97,29 +111,26 @@ export class Config {
|
|||
}
|
||||
}
|
||||
|
||||
private static langServerBinarySource(
|
||||
ctx: vscode.ExtensionContext,
|
||||
config: vscode.WorkspaceConfiguration
|
||||
): null | BinarySource {
|
||||
const langServerPath = RA_LSP_DEBUG ?? config.get<null | string>("raLspServerPath");
|
||||
get serverSource(): null | BinarySource {
|
||||
const serverPath = RA_LSP_DEBUG ?? this.cfg.get<null | string>("raLspServerPath");
|
||||
|
||||
if (langServerPath) {
|
||||
if (serverPath) {
|
||||
return {
|
||||
type: BinarySource.Type.ExplicitPath,
|
||||
path: Config.expandPathResolving(langServerPath)
|
||||
path: Config.replaceTildeWithHomeDir(serverPath)
|
||||
};
|
||||
}
|
||||
|
||||
const prebuiltBinaryName = Config.prebuiltLangServerFileName(
|
||||
process.platform, process.arch
|
||||
);
|
||||
const prebuiltBinaryName = this.prebuiltServerFileName;
|
||||
|
||||
if (!prebuiltBinaryName) return null;
|
||||
|
||||
return {
|
||||
type: BinarySource.Type.GithubRelease,
|
||||
dir: ctx.globalStoragePath,
|
||||
dir: this.ctx.globalStoragePath,
|
||||
file: prebuiltBinaryName,
|
||||
storage: this.ctx.globalState,
|
||||
version: Config.extensionVersion,
|
||||
repo: {
|
||||
name: "rust-analyzer",
|
||||
owner: "rust-analyzer",
|
||||
|
@ -127,158 +138,35 @@ export class Config {
|
|||
};
|
||||
}
|
||||
|
||||
// We don't do runtime config validation here for simplicity. More on stackoverflow:
|
||||
// https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension
|
||||
|
||||
// FIXME: revisit the logic for `if (.has(...)) config.get(...)` set default
|
||||
// values only in one place (i.e. remove default values from non-readonly members declarations)
|
||||
private refresh(ctx: vscode.ExtensionContext) {
|
||||
const config = vscode.workspace.getConfiguration('rust-analyzer');
|
||||
get highlightingOn() { return this.cfg.get("highlightingOn") as boolean; }
|
||||
get rainbowHighlightingOn() { return this.cfg.get("rainbowHighlightingOn") as boolean; }
|
||||
get lruCapacity() { return this.cfg.get("lruCapacity") as null | number; }
|
||||
get displayInlayHints() { return this.cfg.get("displayInlayHints") as boolean; }
|
||||
get maxInlayHintLength() { return this.cfg.get("maxInlayHintLength") as number; }
|
||||
get excludeGlobs() { return this.cfg.get("excludeGlobs") as string[]; }
|
||||
get useClientWatching() { return this.cfg.get("useClientWatching") as boolean; }
|
||||
get featureFlags() { return this.cfg.get("featureFlags") as Record<string, boolean>; }
|
||||
|
||||
let requireReloadMessage = null;
|
||||
|
||||
if (config.has('highlightingOn')) {
|
||||
this.highlightingOn = config.get('highlightingOn') as boolean;
|
||||
}
|
||||
|
||||
if (config.has('rainbowHighlightingOn')) {
|
||||
this.rainbowHighlightingOn = config.get(
|
||||
'rainbowHighlightingOn',
|
||||
) as boolean;
|
||||
}
|
||||
|
||||
if (config.has('enableEnhancedTyping')) {
|
||||
this.enableEnhancedTyping = config.get(
|
||||
'enableEnhancedTyping',
|
||||
) as boolean;
|
||||
|
||||
if (this.prevEnhancedTyping === null) {
|
||||
this.prevEnhancedTyping = this.enableEnhancedTyping;
|
||||
}
|
||||
} else if (this.prevEnhancedTyping === null) {
|
||||
this.prevEnhancedTyping = this.enableEnhancedTyping;
|
||||
}
|
||||
|
||||
if (this.prevEnhancedTyping !== this.enableEnhancedTyping) {
|
||||
requireReloadMessage =
|
||||
'Changing enhanced typing setting requires a reload';
|
||||
this.prevEnhancedTyping = this.enableEnhancedTyping;
|
||||
}
|
||||
|
||||
this.langServerSource = Config.langServerBinarySource(ctx, config);
|
||||
|
||||
if (config.has('cargo-watch.enable')) {
|
||||
this.cargoWatchOptions.enable = config.get<boolean>(
|
||||
'cargo-watch.enable',
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
if (config.has('cargo-watch.arguments')) {
|
||||
this.cargoWatchOptions.arguments = config.get<string[]>(
|
||||
'cargo-watch.arguments',
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
if (config.has('cargo-watch.command')) {
|
||||
this.cargoWatchOptions.command = config.get<string>(
|
||||
'cargo-watch.command',
|
||||
'',
|
||||
);
|
||||
}
|
||||
|
||||
if (config.has('cargo-watch.allTargets')) {
|
||||
this.cargoWatchOptions.allTargets = config.get<boolean>(
|
||||
'cargo-watch.allTargets',
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
if (config.has('lruCapacity')) {
|
||||
this.lruCapacity = config.get('lruCapacity') as number;
|
||||
}
|
||||
|
||||
if (config.has('displayInlayHints')) {
|
||||
this.displayInlayHints = config.get('displayInlayHints') as boolean;
|
||||
}
|
||||
if (config.has('maxInlayHintLength')) {
|
||||
this.maxInlayHintLength = config.get(
|
||||
'maxInlayHintLength',
|
||||
) as number;
|
||||
}
|
||||
if (config.has('excludeGlobs')) {
|
||||
this.excludeGlobs = config.get('excludeGlobs') || [];
|
||||
}
|
||||
if (config.has('useClientWatching')) {
|
||||
this.useClientWatching = config.get('useClientWatching') || true;
|
||||
}
|
||||
if (config.has('featureFlags')) {
|
||||
this.featureFlags = config.get('featureFlags') || {};
|
||||
}
|
||||
if (config.has('withSysroot')) {
|
||||
this.withSysroot = config.get('withSysroot') || false;
|
||||
}
|
||||
|
||||
if (config.has('cargoFeatures.noDefaultFeatures')) {
|
||||
this.cargoFeatures.noDefaultFeatures = config.get(
|
||||
'cargoFeatures.noDefaultFeatures',
|
||||
false,
|
||||
);
|
||||
}
|
||||
if (config.has('cargoFeatures.allFeatures')) {
|
||||
this.cargoFeatures.allFeatures = config.get(
|
||||
'cargoFeatures.allFeatures',
|
||||
true,
|
||||
);
|
||||
}
|
||||
if (config.has('cargoFeatures.features')) {
|
||||
this.cargoFeatures.features = config.get(
|
||||
'cargoFeatures.features',
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
this.prevCargoFeatures !== null &&
|
||||
(this.cargoFeatures.allFeatures !==
|
||||
this.prevCargoFeatures.allFeatures ||
|
||||
this.cargoFeatures.noDefaultFeatures !==
|
||||
this.prevCargoFeatures.noDefaultFeatures ||
|
||||
this.cargoFeatures.features.length !==
|
||||
this.prevCargoFeatures.features.length ||
|
||||
this.cargoFeatures.features.some(
|
||||
(v, i) => v !== this.prevCargoFeatures!.features[i],
|
||||
))
|
||||
) {
|
||||
requireReloadMessage = 'Changing cargo features requires a reload';
|
||||
}
|
||||
this.prevCargoFeatures = { ...this.cargoFeatures };
|
||||
|
||||
if (this.prevCargoWatchOptions !== null) {
|
||||
const changed =
|
||||
this.cargoWatchOptions.enable !== this.prevCargoWatchOptions.enable ||
|
||||
this.cargoWatchOptions.command !== this.prevCargoWatchOptions.command ||
|
||||
this.cargoWatchOptions.allTargets !== this.prevCargoWatchOptions.allTargets ||
|
||||
this.cargoWatchOptions.arguments.length !== this.prevCargoWatchOptions.arguments.length ||
|
||||
this.cargoWatchOptions.arguments.some(
|
||||
(v, i) => v !== this.prevCargoWatchOptions!.arguments[i],
|
||||
);
|
||||
if (changed) {
|
||||
requireReloadMessage = 'Changing cargo-watch options requires a reload';
|
||||
}
|
||||
}
|
||||
this.prevCargoWatchOptions = { ...this.cargoWatchOptions };
|
||||
|
||||
if (requireReloadMessage !== null) {
|
||||
const reloadAction = 'Reload now';
|
||||
vscode.window
|
||||
.showInformationMessage(requireReloadMessage, reloadAction)
|
||||
.then(selectedAction => {
|
||||
if (selectedAction === reloadAction) {
|
||||
vscode.commands.executeCommand(
|
||||
'workbench.action.reloadWindow',
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
get cargoWatchOptions(): CargoWatchOptions {
|
||||
return {
|
||||
enable: this.cfg.get("cargo-watch.enable") as boolean,
|
||||
arguments: this.cfg.get("cargo-watch.arguments") as string[],
|
||||
allTargets: this.cfg.get("cargo-watch.allTargets") as boolean,
|
||||
command: this.cfg.get("cargo-watch.command") as string,
|
||||
};
|
||||
}
|
||||
|
||||
get cargoFeatures(): CargoFeatures {
|
||||
return {
|
||||
noDefaultFeatures: this.cfg.get("cargoFeatures.noDefaultFeatures") as boolean,
|
||||
allFeatures: this.cfg.get("cargoFeatures.allFeatures") as boolean,
|
||||
features: this.cfg.get("cargoFeatures.features") as string[],
|
||||
};
|
||||
}
|
||||
|
||||
// for internal use
|
||||
get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; }
|
||||
}
|
||||
|
|
|
@ -60,6 +60,10 @@ export class Ctx {
|
|||
this.pushCleanup(d);
|
||||
}
|
||||
|
||||
get globalState(): vscode.Memento {
|
||||
return this.extCtx.globalState;
|
||||
}
|
||||
|
||||
get subscriptions(): Disposable[] {
|
||||
return this.extCtx.subscriptions;
|
||||
}
|
||||
|
|
58
editors/code/src/installation/download_artifact.ts
Normal file
58
editors/code/src/installation/download_artifact.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
import * as vscode from "vscode";
|
||||
import * as path from "path";
|
||||
import { promises as fs } from "fs";
|
||||
import { strict as assert } from "assert";
|
||||
|
||||
import { ArtifactReleaseInfo } from "./interfaces";
|
||||
import { downloadFile } from "./download_file";
|
||||
import { throttle } from "throttle-debounce";
|
||||
|
||||
/**
|
||||
* Downloads artifact from given `downloadUrl`.
|
||||
* Creates `installationDir` if it is not yet created and put the artifact under
|
||||
* `artifactFileName`.
|
||||
* Displays info about the download progress in an info message printing the name
|
||||
* of the artifact as `displayName`.
|
||||
*/
|
||||
export async function downloadArtifact(
|
||||
{downloadUrl, releaseName}: ArtifactReleaseInfo,
|
||||
artifactFileName: string,
|
||||
installationDir: string,
|
||||
displayName: string,
|
||||
) {
|
||||
await fs.mkdir(installationDir).catch(err => assert.strictEqual(
|
||||
err?.code,
|
||||
"EEXIST",
|
||||
`Couldn't create directory "${installationDir}" to download `+
|
||||
`${artifactFileName} artifact: ${err.message}`
|
||||
));
|
||||
|
||||
const installationPath = path.join(installationDir, artifactFileName);
|
||||
|
||||
console.time(`Downloading ${artifactFileName}`);
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
cancellable: false, // FIXME: add support for canceling download?
|
||||
title: `Downloading ${displayName} (${releaseName})`
|
||||
},
|
||||
async (progress, _cancellationToken) => {
|
||||
let lastPrecentage = 0;
|
||||
const filePermissions = 0o755; // (rwx, r_x, r_x)
|
||||
await downloadFile(downloadUrl, installationPath, filePermissions, throttle(
|
||||
200,
|
||||
/* noTrailing: */ true,
|
||||
(readBytes, totalBytes) => {
|
||||
const newPercentage = (readBytes / totalBytes) * 100;
|
||||
progress.report({
|
||||
message: newPercentage.toFixed(0) + "%",
|
||||
increment: newPercentage - lastPrecentage
|
||||
});
|
||||
|
||||
lastPrecentage = newPercentage;
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
console.timeEnd(`Downloading ${artifactFileName}`);
|
||||
}
|
|
@ -1,26 +1,32 @@
|
|||
import fetch from "node-fetch";
|
||||
import { GithubRepo, ArtifactMetadata } from "./interfaces";
|
||||
import { GithubRepo, ArtifactReleaseInfo } from "./interfaces";
|
||||
|
||||
const GITHUB_API_ENDPOINT_URL = "https://api.github.com";
|
||||
|
||||
|
||||
/**
|
||||
* Fetches the latest release from GitHub `repo` and returns metadata about
|
||||
* `artifactFileName` shipped with this release or `null` if no such artifact was published.
|
||||
* Fetches the release with `releaseTag` (or just latest release when not specified)
|
||||
* from GitHub `repo` and returns metadata about `artifactFileName` shipped with
|
||||
* this release or `null` if no such artifact was published.
|
||||
*/
|
||||
export async function fetchLatestArtifactMetadata(
|
||||
repo: GithubRepo, artifactFileName: string
|
||||
): Promise<null | ArtifactMetadata> {
|
||||
export async function fetchArtifactReleaseInfo(
|
||||
repo: GithubRepo, artifactFileName: string, releaseTag?: string
|
||||
): Promise<null | ArtifactReleaseInfo> {
|
||||
|
||||
const repoOwner = encodeURIComponent(repo.owner);
|
||||
const repoName = encodeURIComponent(repo.name);
|
||||
|
||||
const apiEndpointPath = `/repos/${repoOwner}/${repoName}/releases/latest`;
|
||||
const apiEndpointPath = releaseTag
|
||||
? `/repos/${repoOwner}/${repoName}/releases/tags/${releaseTag}`
|
||||
: `/repos/${repoOwner}/${repoName}/releases/latest`;
|
||||
|
||||
const requestUrl = GITHUB_API_ENDPOINT_URL + apiEndpointPath;
|
||||
|
||||
// We skip runtime type checks for simplicity (here we cast from `any` to `GithubRelease`)
|
||||
|
||||
console.log("Issuing request for released artifacts metadata to", requestUrl);
|
||||
|
||||
// FIXME: handle non-ok response
|
||||
const response: GithubRelease = await fetch(requestUrl, {
|
||||
headers: { Accept: "application/vnd.github.v3+json" }
|
||||
})
|
|
@ -1,3 +1,5 @@
|
|||
import * as vscode from "vscode";
|
||||
|
||||
export interface GithubRepo {
|
||||
name: string;
|
||||
owner: string;
|
||||
|
@ -6,7 +8,7 @@ export interface GithubRepo {
|
|||
/**
|
||||
* Metadata about particular artifact retrieved from GitHub releases.
|
||||
*/
|
||||
export interface ArtifactMetadata {
|
||||
export interface ArtifactReleaseInfo {
|
||||
releaseName: string;
|
||||
downloadUrl: string;
|
||||
}
|
||||
|
@ -50,6 +52,17 @@ export namespace BinarySource {
|
|||
* and in local `.dir`.
|
||||
*/
|
||||
file: string;
|
||||
|
||||
/**
|
||||
* Tag of github release that denotes a version required by this extension.
|
||||
*/
|
||||
version: string;
|
||||
|
||||
/**
|
||||
* Object that provides `get()/update()` operations to store metadata
|
||||
* about the actual binary, e.g. its actual version.
|
||||
*/
|
||||
storage: vscode.Memento;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,148 +0,0 @@
|
|||
import * as vscode from "vscode";
|
||||
import * as path from "path";
|
||||
import { strict as assert } from "assert";
|
||||
import { promises as fs } from "fs";
|
||||
import { promises as dns } from "dns";
|
||||
import { spawnSync } from "child_process";
|
||||
import { throttle } from "throttle-debounce";
|
||||
|
||||
import { BinarySource } from "./interfaces";
|
||||
import { fetchLatestArtifactMetadata } from "./fetch_latest_artifact_metadata";
|
||||
import { downloadFile } from "./download_file";
|
||||
|
||||
export async function downloadLatestLanguageServer(
|
||||
{file: artifactFileName, dir: installationDir, repo}: BinarySource.GithubRelease
|
||||
) {
|
||||
const { releaseName, downloadUrl } = (await fetchLatestArtifactMetadata(
|
||||
repo, artifactFileName
|
||||
))!;
|
||||
|
||||
await fs.mkdir(installationDir).catch(err => assert.strictEqual(
|
||||
err?.code,
|
||||
"EEXIST",
|
||||
`Couldn't create directory "${installationDir}" to download `+
|
||||
`language server binary: ${err.message}`
|
||||
));
|
||||
|
||||
const installationPath = path.join(installationDir, artifactFileName);
|
||||
|
||||
console.time("Downloading ra_lsp_server");
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
cancellable: false, // FIXME: add support for canceling download?
|
||||
title: `Downloading language server (${releaseName})`
|
||||
},
|
||||
async (progress, _cancellationToken) => {
|
||||
let lastPrecentage = 0;
|
||||
const filePermissions = 0o755; // (rwx, r_x, r_x)
|
||||
await downloadFile(downloadUrl, installationPath, filePermissions, throttle(
|
||||
200,
|
||||
/* noTrailing: */ true,
|
||||
(readBytes, totalBytes) => {
|
||||
const newPercentage = (readBytes / totalBytes) * 100;
|
||||
progress.report({
|
||||
message: newPercentage.toFixed(0) + "%",
|
||||
increment: newPercentage - lastPrecentage
|
||||
});
|
||||
|
||||
lastPrecentage = newPercentage;
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
console.timeEnd("Downloading ra_lsp_server");
|
||||
}
|
||||
export async function ensureLanguageServerBinary(
|
||||
langServerSource: null | BinarySource
|
||||
): Promise<null | string> {
|
||||
|
||||
if (!langServerSource) {
|
||||
vscode.window.showErrorMessage(
|
||||
"Unfortunately we don't ship binaries for your platform yet. " +
|
||||
"You need to manually clone rust-analyzer repository and " +
|
||||
"run `cargo xtask install --server` to build the language server from sources. " +
|
||||
"If you feel that your platform should be supported, please create an issue " +
|
||||
"about that [here](https://github.com/rust-analyzer/rust-analyzer/issues) and we " +
|
||||
"will consider it."
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (langServerSource.type) {
|
||||
case BinarySource.Type.ExplicitPath: {
|
||||
if (isBinaryAvailable(langServerSource.path)) {
|
||||
return langServerSource.path;
|
||||
}
|
||||
|
||||
vscode.window.showErrorMessage(
|
||||
`Unable to run ${langServerSource.path} binary. ` +
|
||||
`To use the pre-built language server, set "rust-analyzer.raLspServerPath" ` +
|
||||
"value to `null` or remove it from the settings to use it by default."
|
||||
);
|
||||
return null;
|
||||
}
|
||||
case BinarySource.Type.GithubRelease: {
|
||||
const prebuiltBinaryPath = path.join(langServerSource.dir, langServerSource.file);
|
||||
|
||||
if (isBinaryAvailable(prebuiltBinaryPath)) {
|
||||
return prebuiltBinaryPath;
|
||||
}
|
||||
|
||||
const userResponse = await vscode.window.showInformationMessage(
|
||||
"Language server binary for rust-analyzer was not found. " +
|
||||
"Do you want to download it now?",
|
||||
"Download now", "Cancel"
|
||||
);
|
||||
if (userResponse !== "Download now") return null;
|
||||
|
||||
try {
|
||||
await downloadLatestLanguageServer(langServerSource);
|
||||
} catch (err) {
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to download language server from ${langServerSource.repo.name} ` +
|
||||
`GitHub repository: ${err.message}`
|
||||
);
|
||||
|
||||
console.error(err);
|
||||
|
||||
dns.resolve('example.com').then(
|
||||
addrs => console.log("DNS resolution for example.com was successful", addrs),
|
||||
err => {
|
||||
console.error(
|
||||
"DNS resolution for example.com failed, " +
|
||||
"there might be an issue with Internet availability"
|
||||
);
|
||||
console.error(err);
|
||||
}
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isBinaryAvailable(prebuiltBinaryPath)) assert(false,
|
||||
`Downloaded language server binary is not functional.` +
|
||||
`Downloaded from: ${JSON.stringify(langServerSource)}`
|
||||
);
|
||||
|
||||
|
||||
vscode.window.showInformationMessage(
|
||||
"Rust analyzer language server was successfully installed 🦀"
|
||||
);
|
||||
|
||||
return prebuiltBinaryPath;
|
||||
}
|
||||
}
|
||||
|
||||
function isBinaryAvailable(binaryPath: string) {
|
||||
const res = spawnSync(binaryPath, ["--version"]);
|
||||
|
||||
// ACHTUNG! `res` type declaration is inherently wrong, see
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221
|
||||
|
||||
console.log("Checked binary availablity via --version", res);
|
||||
console.log(binaryPath, "--version output:", res.output?.map(String));
|
||||
|
||||
return res.status === 0;
|
||||
}
|
||||
}
|
124
editors/code/src/installation/server.ts
Normal file
124
editors/code/src/installation/server.ts
Normal file
|
@ -0,0 +1,124 @@
|
|||
import * as vscode from "vscode";
|
||||
import * as path from "path";
|
||||
import { strict as assert } from "assert";
|
||||
import { promises as dns } from "dns";
|
||||
import { spawnSync } from "child_process";
|
||||
|
||||
import { BinarySource } from "./interfaces";
|
||||
import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info";
|
||||
import { downloadArtifact } from "./download_artifact";
|
||||
|
||||
export async function ensureServerBinary(source: null | BinarySource): Promise<null | string> {
|
||||
if (!source) {
|
||||
vscode.window.showErrorMessage(
|
||||
"Unfortunately we don't ship binaries for your platform yet. " +
|
||||
"You need to manually clone rust-analyzer repository and " +
|
||||
"run `cargo xtask install --server` to build the language server from sources. " +
|
||||
"If you feel that your platform should be supported, please create an issue " +
|
||||
"about that [here](https://github.com/rust-analyzer/rust-analyzer/issues) and we " +
|
||||
"will consider it."
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (source.type) {
|
||||
case BinarySource.Type.ExplicitPath: {
|
||||
if (isBinaryAvailable(source.path)) {
|
||||
return source.path;
|
||||
}
|
||||
|
||||
vscode.window.showErrorMessage(
|
||||
`Unable to run ${source.path} binary. ` +
|
||||
`To use the pre-built language server, set "rust-analyzer.raLspServerPath" ` +
|
||||
"value to `null` or remove it from the settings to use it by default."
|
||||
);
|
||||
return null;
|
||||
}
|
||||
case BinarySource.Type.GithubRelease: {
|
||||
const prebuiltBinaryPath = path.join(source.dir, source.file);
|
||||
|
||||
const installedVersion: null | string = getServerVersion(source.storage);
|
||||
const requiredVersion: string = source.version;
|
||||
|
||||
console.log("Installed version:", installedVersion, "required:", requiredVersion);
|
||||
|
||||
if (isBinaryAvailable(prebuiltBinaryPath) && installedVersion == requiredVersion) {
|
||||
// FIXME: check for new releases and notify the user to update if possible
|
||||
return prebuiltBinaryPath;
|
||||
}
|
||||
|
||||
const userResponse = await vscode.window.showInformationMessage(
|
||||
`Language server version ${source.version} for rust-analyzer is not installed. ` +
|
||||
"Do you want to download it now?",
|
||||
"Download now", "Cancel"
|
||||
);
|
||||
if (userResponse !== "Download now") return null;
|
||||
|
||||
if (!await downloadServer(source)) return null;
|
||||
|
||||
return prebuiltBinaryPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadServer(source: BinarySource.GithubRelease): Promise<boolean> {
|
||||
try {
|
||||
const releaseInfo = (await fetchArtifactReleaseInfo(source.repo, source.file, source.version))!;
|
||||
|
||||
await downloadArtifact(releaseInfo, source.file, source.dir, "language server");
|
||||
await setServerVersion(source.storage, releaseInfo.releaseName);
|
||||
} catch (err) {
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to download language server from ${source.repo.name} ` +
|
||||
`GitHub repository: ${err.message}`
|
||||
);
|
||||
|
||||
console.error(err);
|
||||
|
||||
dns.resolve('example.com').then(
|
||||
addrs => console.log("DNS resolution for example.com was successful", addrs),
|
||||
err => {
|
||||
console.error(
|
||||
"DNS resolution for example.com failed, " +
|
||||
"there might be an issue with Internet availability"
|
||||
);
|
||||
console.error(err);
|
||||
}
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isBinaryAvailable(path.join(source.dir, source.file))) assert(false,
|
||||
`Downloaded language server binary is not functional.` +
|
||||
`Downloaded from: ${JSON.stringify(source, null, 4)}`
|
||||
);
|
||||
|
||||
vscode.window.showInformationMessage(
|
||||
"Rust analyzer language server was successfully installed 🦀"
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isBinaryAvailable(binaryPath: string): boolean {
|
||||
const res = spawnSync(binaryPath, ["--version"]);
|
||||
|
||||
// ACHTUNG! `res` type declaration is inherently wrong, see
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221
|
||||
|
||||
console.log("Checked binary availablity via --version", res);
|
||||
console.log(binaryPath, "--version output:", res.output?.map(String));
|
||||
|
||||
return res.status === 0;
|
||||
}
|
||||
|
||||
function getServerVersion(storage: vscode.Memento): null | string {
|
||||
const version = storage.get<null | string>("server-version", null);
|
||||
console.log("Get server-version:", version);
|
||||
return version;
|
||||
}
|
||||
|
||||
async function setServerVersion(storage: vscode.Memento, version: string): Promise<void> {
|
||||
console.log("Set server-version:", version);
|
||||
await storage.update("server-version", version.toString());
|
||||
}
|
|
@ -66,9 +66,9 @@ class StatusDisplay implements Disposable {
|
|||
|
||||
refreshLabel() {
|
||||
if (this.packageName) {
|
||||
this.statusBarItem!.text = `${spinnerFrames[this.i]} cargo ${this.command} [${this.packageName}]`;
|
||||
this.statusBarItem.text = `${spinnerFrames[this.i]} cargo ${this.command} [${this.packageName}]`;
|
||||
} else {
|
||||
this.statusBarItem!.text = `${spinnerFrames[this.i]} cargo ${this.command}`;
|
||||
this.statusBarItem.text = `${spinnerFrames[this.i]} cargo ${this.command}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
use std::process::{Command, Output, Stdio};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use crate::project_root;
|
||||
|
||||
pub struct Cmd<'a> {
|
||||
pub unix: &'a str,
|
||||
pub windows: &'a str,
|
||||
pub work_dir: &'a str,
|
||||
}
|
||||
|
||||
impl Cmd<'_> {
|
||||
pub fn run(self) -> Result<()> {
|
||||
if cfg!(windows) {
|
||||
run(self.windows, self.work_dir)
|
||||
} else {
|
||||
run(self.unix, self.work_dir)
|
||||
}
|
||||
}
|
||||
pub fn run_with_output(self) -> Result<String> {
|
||||
if cfg!(windows) {
|
||||
run_with_output(self.windows, self.work_dir)
|
||||
} else {
|
||||
run_with_output(self.unix, self.work_dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(cmdline: &str, dir: &str) -> Result<()> {
|
||||
do_run(cmdline, dir, &mut |c| {
|
||||
c.stdout(Stdio::inherit());
|
||||
})
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub fn run_with_output(cmdline: &str, dir: &str) -> Result<String> {
|
||||
let output = do_run(cmdline, dir, &mut |_| {})?;
|
||||
let stdout = String::from_utf8(output.stdout)?;
|
||||
let stdout = stdout.trim().to_string();
|
||||
Ok(stdout)
|
||||
}
|
||||
|
||||
fn do_run(cmdline: &str, dir: &str, f: &mut dyn FnMut(&mut Command)) -> Result<Output> {
|
||||
eprintln!("\nwill run: {}", cmdline);
|
||||
let proj_dir = project_root().join(dir);
|
||||
let mut args = cmdline.split_whitespace();
|
||||
let exec = args.next().unwrap();
|
||||
let mut cmd = Command::new(exec);
|
||||
f(cmd.args(args).current_dir(proj_dir).stderr(Stdio::inherit()));
|
||||
let output = cmd.output().with_context(|| format!("running `{}`", cmdline))?;
|
||||
if !output.status.success() {
|
||||
anyhow::bail!("`{}` exited with {}", cmdline, output.status);
|
||||
}
|
||||
Ok(output)
|
||||
}
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
use std::{env, path::PathBuf, str};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{bail, format_err, Context, Result};
|
||||
|
||||
use crate::cmd::{run, run_with_output, Cmd};
|
||||
use crate::not_bash::{ls, pushd, rm, run};
|
||||
|
||||
// Latest stable, feel free to send a PR if this lags behind.
|
||||
const REQUIRED_RUST_VERSION: u32 = 41;
|
||||
|
@ -55,7 +55,7 @@ fn fix_path_for_mac() -> Result<()> {
|
|||
const ROOT_DIR: &str = "";
|
||||
let home_dir = match env::var("HOME") {
|
||||
Ok(home) => home,
|
||||
Err(e) => anyhow::bail!("Failed getting HOME from environment with error: {}.", e),
|
||||
Err(e) => bail!("Failed getting HOME from environment with error: {}.", e),
|
||||
};
|
||||
|
||||
[ROOT_DIR, &home_dir]
|
||||
|
@ -69,7 +69,7 @@ fn fix_path_for_mac() -> Result<()> {
|
|||
if !vscode_path.is_empty() {
|
||||
let vars = match env::var_os("PATH") {
|
||||
Some(path) => path,
|
||||
None => anyhow::bail!("Could not get PATH variable from env."),
|
||||
None => bail!("Could not get PATH variable from env."),
|
||||
};
|
||||
|
||||
let mut paths = env::split_paths(&vars).collect::<Vec<_>>();
|
||||
|
@ -82,84 +82,61 @@ fn fix_path_for_mac() -> Result<()> {
|
|||
}
|
||||
|
||||
fn install_client(ClientOpt::VsCode: ClientOpt) -> Result<()> {
|
||||
let npm_version = Cmd {
|
||||
unix: r"npm --version",
|
||||
windows: r"cmd.exe /c npm --version",
|
||||
work_dir: "./editors/code",
|
||||
}
|
||||
.run();
|
||||
let _dir = pushd("./editors/code");
|
||||
|
||||
if npm_version.is_err() {
|
||||
eprintln!("\nERROR: `npm --version` failed, `npm` is required to build the VS Code plugin")
|
||||
}
|
||||
|
||||
Cmd { unix: r"npm install", windows: r"cmd.exe /c npm install", work_dir: "./editors/code" }
|
||||
.run()?;
|
||||
Cmd {
|
||||
unix: r"npm run package --scripts-prepend-node-path",
|
||||
windows: r"cmd.exe /c npm run package",
|
||||
work_dir: "./editors/code",
|
||||
}
|
||||
.run()?;
|
||||
|
||||
let code_binary = ["code", "code-insiders", "codium", "code-oss"].iter().find(|bin| {
|
||||
Cmd {
|
||||
unix: &format!("{} --version", bin),
|
||||
windows: &format!("cmd.exe /c {}.cmd --version", bin),
|
||||
work_dir: "./editors/code",
|
||||
}
|
||||
.run()
|
||||
.is_ok()
|
||||
});
|
||||
|
||||
let code_binary = match code_binary {
|
||||
Some(it) => it,
|
||||
None => anyhow::bail!("Can't execute `code --version`. Perhaps it is not in $PATH?"),
|
||||
let find_code = |f: fn(&str) -> bool| -> Result<&'static str> {
|
||||
["code", "code-insiders", "codium", "code-oss"]
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|bin| f(bin))
|
||||
.ok_or_else(|| {
|
||||
format_err!("Can't execute `code --version`. Perhaps it is not in $PATH?")
|
||||
})
|
||||
};
|
||||
|
||||
Cmd {
|
||||
unix: &format!(r"{} --install-extension ./rust-analyzer-0.1.0.vsix --force", code_binary),
|
||||
windows: &format!(
|
||||
r"cmd.exe /c {}.cmd --install-extension ./rust-analyzer-0.1.0.vsix --force",
|
||||
code_binary
|
||||
),
|
||||
work_dir: "./editors/code",
|
||||
}
|
||||
.run()?;
|
||||
let installed_extensions;
|
||||
if cfg!(unix) {
|
||||
run!("npm --version").context("`npm` is required to build the VS Code plugin")?;
|
||||
run!("npm install")?;
|
||||
|
||||
let installed_extensions = Cmd {
|
||||
unix: &format!(r"{} --list-extensions", code_binary),
|
||||
windows: &format!(r"cmd.exe /c {}.cmd --list-extensions", code_binary),
|
||||
work_dir: ".",
|
||||
let vsix_pkg = {
|
||||
rm("*.vsix")?;
|
||||
run!("npm run package --scripts-prepend-node-path")?;
|
||||
ls("*.vsix")?.pop().unwrap()
|
||||
};
|
||||
|
||||
let code = find_code(|bin| run!("{} --version", bin).is_ok())?;
|
||||
run!("{} --install-extension {} --force", code, vsix_pkg.display())?;
|
||||
installed_extensions = run!("{} --list-extensions", code; echo = false)?;
|
||||
} else {
|
||||
run!("cmd.exe /c npm --version")
|
||||
.context("`npm` is required to build the VS Code plugin")?;
|
||||
run!("cmd.exe /c npm install")?;
|
||||
|
||||
let vsix_pkg = {
|
||||
rm("*.vsix")?;
|
||||
run!("cmd.exe /c npm run package")?;
|
||||
ls("*.vsix")?.pop().unwrap()
|
||||
};
|
||||
|
||||
let code = find_code(|bin| run!("cmd.exe /c {}.cmd --version", bin).is_ok())?;
|
||||
run!(r"cmd.exe /c {}.cmd --install-extension {} --force", code, vsix_pkg.display())?;
|
||||
installed_extensions = run!("cmd.exe /c {}.cmd --list-extensions", code; echo = false)?;
|
||||
}
|
||||
.run_with_output()?;
|
||||
|
||||
if !installed_extensions.contains("rust-analyzer") {
|
||||
anyhow::bail!(
|
||||
bail!(
|
||||
"Could not install the Visual Studio Code extension. \
|
||||
Please make sure you have at least NodeJS 10.x together with the latest version of VS Code installed and try again."
|
||||
Please make sure you have at least NodeJS 12.x together with the latest version of VS Code installed and try again."
|
||||
);
|
||||
}
|
||||
|
||||
if installed_extensions.contains("ra-lsp") {
|
||||
Cmd {
|
||||
unix: &format!(r"{} --uninstall-extension matklad.ra-lsp", code_binary),
|
||||
windows: &format!(
|
||||
r"cmd.exe /c {}.cmd --uninstall-extension matklad.ra-lsp",
|
||||
code_binary
|
||||
),
|
||||
work_dir: "./editors/code",
|
||||
}
|
||||
.run()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install_server(opts: ServerOpt) -> Result<()> {
|
||||
let mut old_rust = false;
|
||||
if let Ok(stdout) = run_with_output("cargo --version", ".") {
|
||||
println!("{}", stdout);
|
||||
if let Ok(stdout) = run!("cargo --version") {
|
||||
if !check_version(&stdout, REQUIRED_RUST_VERSION) {
|
||||
old_rust = true;
|
||||
}
|
||||
|
@ -172,20 +149,17 @@ fn install_server(opts: ServerOpt) -> Result<()> {
|
|||
)
|
||||
}
|
||||
|
||||
let res = if opts.jemalloc {
|
||||
run("cargo install --path crates/ra_lsp_server --locked --force --features jemalloc", ".")
|
||||
} else {
|
||||
run("cargo install --path crates/ra_lsp_server --locked --force", ".")
|
||||
};
|
||||
let jemalloc = if opts.jemalloc { "--features jemalloc" } else { "" };
|
||||
let res = run!("cargo install --path crates/ra_lsp_server --locked --force {}", jemalloc);
|
||||
|
||||
if res.is_err() && old_rust {
|
||||
eprintln!(
|
||||
"\nWARNING: at least rust 1.{}.0 is required to compile rust-analyzer\n",
|
||||
REQUIRED_RUST_VERSION,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
res
|
||||
res.map(drop)
|
||||
}
|
||||
|
||||
fn check_version(version_output: &str, min_minor_version: u32) -> bool {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! FIXME: write short doc here
|
||||
|
||||
mod cmd;
|
||||
pub mod not_bash;
|
||||
pub mod install;
|
||||
pub mod pre_commit;
|
||||
|
||||
|
@ -9,15 +9,15 @@ mod ast_src;
|
|||
|
||||
use anyhow::Context;
|
||||
use std::{
|
||||
env, fs,
|
||||
env,
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
cmd::{run, run_with_output},
|
||||
codegen::Mode,
|
||||
not_bash::{fs2, pushd, rm_rf, run},
|
||||
};
|
||||
|
||||
pub use anyhow::Result;
|
||||
|
@ -38,9 +38,9 @@ pub fn run_rustfmt(mode: Mode) -> Result<()> {
|
|||
ensure_rustfmt()?;
|
||||
|
||||
if mode == Mode::Verify {
|
||||
run(&format!("rustup run {} -- cargo fmt -- --check", TOOLCHAIN), ".")?;
|
||||
run!("rustup run {} -- cargo fmt -- --check", TOOLCHAIN)?;
|
||||
} else {
|
||||
run(&format!("rustup run {} -- cargo fmt", TOOLCHAIN), ".")?;
|
||||
run!("rustup run {} -- cargo fmt", TOOLCHAIN)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -70,8 +70,9 @@ fn ensure_rustfmt() -> Result<()> {
|
|||
Ok(status) if status.success() => return Ok(()),
|
||||
_ => (),
|
||||
};
|
||||
run(&format!("rustup toolchain install {}", TOOLCHAIN), ".")?;
|
||||
run(&format!("rustup component add rustfmt --toolchain {}", TOOLCHAIN), ".")
|
||||
run!("rustup toolchain install {}", TOOLCHAIN)?;
|
||||
run!("rustup component add rustfmt --toolchain {}", TOOLCHAIN)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_clippy() -> Result<()> {
|
||||
|
@ -92,34 +93,28 @@ pub fn run_clippy() -> Result<()> {
|
|||
"clippy::nonminimal_bool",
|
||||
"clippy::redundant_pattern_matching",
|
||||
];
|
||||
run(
|
||||
&format!(
|
||||
"rustup run {} -- cargo clippy --all-features --all-targets -- -A {}",
|
||||
TOOLCHAIN,
|
||||
allowed_lints.join(" -A ")
|
||||
),
|
||||
".",
|
||||
run!(
|
||||
"rustup run {} -- cargo clippy --all-features --all-targets -- -A {}",
|
||||
TOOLCHAIN,
|
||||
allowed_lints.join(" -A ")
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install_clippy() -> Result<()> {
|
||||
run(&format!("rustup toolchain install {}", TOOLCHAIN), ".")?;
|
||||
run(&format!("rustup component add clippy --toolchain {}", TOOLCHAIN), ".")
|
||||
run!("rustup toolchain install {}", TOOLCHAIN)?;
|
||||
run!("rustup component add clippy --toolchain {}", TOOLCHAIN)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_fuzzer() -> Result<()> {
|
||||
match Command::new("cargo")
|
||||
.args(&["fuzz", "--help"])
|
||||
.stderr(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.status()
|
||||
{
|
||||
Ok(status) if status.success() => (),
|
||||
_ => run("cargo install cargo-fuzz", ".")?,
|
||||
let _d = pushd("./crates/ra_syntax");
|
||||
if run!("cargo fuzz --help").is_err() {
|
||||
run!("cargo install cargo-fuzz")?;
|
||||
};
|
||||
|
||||
run("rustup run nightly -- cargo fuzz run parser", "./crates/ra_syntax")
|
||||
run!("rustup run nightly -- cargo fuzz run parser")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cleans the `./target` dir after the build such that only
|
||||
|
@ -141,7 +136,7 @@ pub fn run_pre_cache() -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
fs::remove_file("./target/.rustc_info.json")?;
|
||||
fs2::remove_file("./target/.rustc_info.json")?;
|
||||
let to_delete = ["ra_", "heavy_test"];
|
||||
for &dir in ["./target/debug/deps", "target/debug/.fingerprint"].iter() {
|
||||
for entry in Path::new(dir).read_dir()? {
|
||||
|
@ -155,22 +150,20 @@ pub fn run_pre_cache() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn rm_rf(path: &Path) -> Result<()> {
|
||||
if path.is_file() { fs::remove_file(path) } else { fs::remove_dir_all(path) }
|
||||
.with_context(|| format!("failed to remove {:?}", path))
|
||||
}
|
||||
pub fn run_release(dry_run: bool) -> Result<()> {
|
||||
if !dry_run {
|
||||
run!("git switch release")?;
|
||||
run!("git fetch upstream")?;
|
||||
run!("git reset --hard upstream/master")?;
|
||||
run!("git push")?;
|
||||
}
|
||||
|
||||
pub fn run_release() -> Result<()> {
|
||||
run("git switch release", ".")?;
|
||||
run("git fetch upstream", ".")?;
|
||||
run("git reset --hard upstream/master", ".")?;
|
||||
run("git push", ".")?;
|
||||
let website_root = project_root().join("../rust-analyzer.github.io");
|
||||
let changelog_dir = website_root.join("./thisweek/_posts");
|
||||
|
||||
let changelog_dir = project_root().join("../rust-analyzer.github.io/thisweek/_posts");
|
||||
|
||||
let today = run_with_output("date --iso", ".")?;
|
||||
let commit = run_with_output("git rev-parse HEAD", ".")?;
|
||||
let changelog_n = fs::read_dir(changelog_dir.as_path())?.count();
|
||||
let today = run!("date --iso")?;
|
||||
let commit = run!("git rev-parse HEAD")?;
|
||||
let changelog_n = fs2::read_dir(changelog_dir.as_path())?.count();
|
||||
|
||||
let contents = format!(
|
||||
"\
|
||||
|
@ -193,7 +186,9 @@ Release: release:{}[]
|
|||
);
|
||||
|
||||
let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n));
|
||||
fs::write(&path, &contents)?;
|
||||
fs2::write(&path, &contents)?;
|
||||
|
||||
fs2::copy(project_root().join("./docs/user/readme.adoc"), website_root.join("manual.adoc"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -93,8 +93,9 @@ FLAGS:
|
|||
run_pre_cache()
|
||||
}
|
||||
"release" => {
|
||||
let dry_run = args.contains("--dry-run");
|
||||
args.finish()?;
|
||||
run_release()
|
||||
run_release(dry_run)
|
||||
}
|
||||
_ => {
|
||||
eprintln!(
|
||||
|
|
165
xtask/src/not_bash.rs
Normal file
165
xtask/src/not_bash.rs
Normal file
|
@ -0,0 +1,165 @@
|
|||
//! A bad shell -- small cross platform module for writing glue code
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
env,
|
||||
ffi::OsStr,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
|
||||
pub mod fs2 {
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
pub fn read_dir<P: AsRef<Path>>(path: P) -> Result<fs::ReadDir> {
|
||||
let path = path.as_ref();
|
||||
fs::read_dir(path).with_context(|| format!("Failed to read {}", path.display()))
|
||||
}
|
||||
|
||||
pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
fs::write(path, contents).with_context(|| format!("Failed to write {}", path.display()))
|
||||
}
|
||||
|
||||
pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<u64> {
|
||||
let from = from.as_ref();
|
||||
let to = to.as_ref();
|
||||
fs::copy(from, to)
|
||||
.with_context(|| format!("Failed to copy {} to {}", from.display(), to.display()))
|
||||
}
|
||||
|
||||
pub fn remove_file<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
fs::remove_file(path).with_context(|| format!("Failed to remove file {}", path.display()))
|
||||
}
|
||||
|
||||
pub fn remove_dir_all<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
fs::remove_dir_all(path).with_context(|| format!("Failed to remove dir {}", path.display()))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! _run {
|
||||
($($expr:expr),*) => {
|
||||
run!($($expr),*; echo = true)
|
||||
};
|
||||
($($expr:expr),* ; echo = $echo:expr) => {
|
||||
$crate::not_bash::run_process(format!($($expr),*), $echo)
|
||||
};
|
||||
}
|
||||
pub(crate) use _run as run;
|
||||
|
||||
pub struct Pushd {
|
||||
_p: (),
|
||||
}
|
||||
|
||||
pub fn pushd(path: impl Into<PathBuf>) -> Pushd {
|
||||
Env::with(|env| env.pushd(path.into()));
|
||||
Pushd { _p: () }
|
||||
}
|
||||
|
||||
impl Drop for Pushd {
|
||||
fn drop(&mut self) {
|
||||
Env::with(|env| env.popd())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rm(glob: &str) -> Result<()> {
|
||||
let cwd = Env::with(|env| env.cwd());
|
||||
ls(glob)?.into_iter().try_for_each(|it| fs::remove_file(cwd.join(it)))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rm_rf(path: impl AsRef<Path>) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
if path.is_file() {
|
||||
fs2::remove_file(path)
|
||||
} else {
|
||||
fs2::remove_dir_all(path)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ls(glob: &str) -> Result<Vec<PathBuf>> {
|
||||
let cwd = Env::with(|env| env.cwd());
|
||||
let mut res = Vec::new();
|
||||
for entry in fs::read_dir(&cwd)? {
|
||||
let entry = entry?;
|
||||
if matches(&entry.file_name(), glob) {
|
||||
let path = entry.path();
|
||||
let path = path.strip_prefix(&cwd).unwrap();
|
||||
res.push(path.to_path_buf())
|
||||
}
|
||||
}
|
||||
return Ok(res);
|
||||
|
||||
fn matches(file_name: &OsStr, glob: &str) -> bool {
|
||||
assert!(glob.starts_with('*'));
|
||||
file_name.to_string_lossy().ends_with(&glob[1..])
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn run_process(cmd: String, echo: bool) -> Result<String> {
|
||||
run_process_inner(&cmd, echo).with_context(|| format!("process `{}` failed", cmd))
|
||||
}
|
||||
|
||||
fn run_process_inner(cmd: &str, echo: bool) -> Result<String> {
|
||||
let cwd = Env::with(|env| env.cwd());
|
||||
let mut args = shelx(cmd);
|
||||
let binary = args.remove(0);
|
||||
|
||||
if echo {
|
||||
println!("> {}", cmd)
|
||||
}
|
||||
|
||||
let output = Command::new(binary)
|
||||
.args(args)
|
||||
.current_dir(cwd)
|
||||
.stdin(Stdio::null())
|
||||
.stderr(Stdio::inherit())
|
||||
.output()?;
|
||||
let stdout = String::from_utf8(output.stdout)?;
|
||||
|
||||
if echo {
|
||||
print!("{}", stdout)
|
||||
}
|
||||
|
||||
if !output.status.success() {
|
||||
bail!("{}", output.status)
|
||||
}
|
||||
|
||||
Ok(stdout.trim().to_string())
|
||||
}
|
||||
|
||||
// FIXME: some real shell lexing here
|
||||
fn shelx(cmd: &str) -> Vec<String> {
|
||||
cmd.split_whitespace().map(|it| it.to_string()).collect()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Env {
|
||||
pushd_stack: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl Env {
|
||||
fn with<F: FnOnce(&mut Env) -> T, T>(f: F) -> T {
|
||||
thread_local! {
|
||||
static ENV: RefCell<Env> = Default::default();
|
||||
}
|
||||
ENV.with(|it| f(&mut *it.borrow_mut()))
|
||||
}
|
||||
|
||||
fn pushd(&mut self, dir: PathBuf) {
|
||||
self.pushd_stack.push(dir)
|
||||
}
|
||||
fn popd(&mut self) {
|
||||
self.pushd_stack.pop().unwrap();
|
||||
}
|
||||
fn cwd(&self) -> PathBuf {
|
||||
self.pushd_stack.last().cloned().unwrap_or_else(|| env::current_dir().unwrap())
|
||||
}
|
||||
}
|
|
@ -4,18 +4,18 @@ use std::{fs, path::PathBuf};
|
|||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use crate::{cmd::run_with_output, project_root, run, run_rustfmt, Mode};
|
||||
use crate::{not_bash::run, project_root, run_rustfmt, Mode};
|
||||
|
||||
// FIXME: if there are changed `.ts` files, also reformat TypeScript (by
|
||||
// shelling out to `npm fmt`).
|
||||
pub fn run_hook() -> Result<()> {
|
||||
run_rustfmt(Mode::Overwrite)?;
|
||||
|
||||
let diff = run_with_output("git diff --diff-filter=MAR --name-only --cached", ".")?;
|
||||
let diff = run!("git diff --diff-filter=MAR --name-only --cached")?;
|
||||
|
||||
let root = project_root();
|
||||
for line in diff.lines() {
|
||||
run(&format!("git update-index --add {}", root.join(line).to_string_lossy()), ".")?;
|
||||
run!("git update-index --add {}", root.join(line).display())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
Loading…
Reference in a new issue