Merge remote-tracking branch 'origin/master'

This commit is contained in:
Dmitry 2020-02-17 00:36:48 +07:00
commit 58e15d12e4
47 changed files with 1414 additions and 1262 deletions

View file

@ -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
View file

@ -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",

View file

@ -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" }

View file

@ -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" }

View file

@ -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)?;

View file

@ -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();

View file

@ -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" => {

View file

@ -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;
}

View file

@ -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 {

View file

@ -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)
),
}
}
}

View file

@ -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)))

View file

@ -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)
),
}
}
}

View file

@ -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 })
}
}

View file

@ -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
);

View file

@ -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);
}

View file

@ -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
"###
);
}

View file

@ -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(

View file

@ -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

View file

@ -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
})
}

View file

@ -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);

View file

@ -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; }

View file

@ -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; }

View file

@ -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; }

View file

@ -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" }

View file

@ -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(())
}

View file

@ -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.

View file

@ -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());
}
}

View file

@ -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
View 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].

View file

@ -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",

View file

@ -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"
}

View file

@ -18,6 +18,7 @@ export default {
external: [...nodeBuiltins, 'vscode'],
output: {
file: './out/main.js',
format: 'cjs'
format: 'cjs',
exports: 'named'
}
};

View file

@ -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;
}

View file

@ -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; }
}

View file

@ -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;
}

View 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}`);
}

View file

@ -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" }
})

View file

@ -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;
}
}

View file

@ -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;
}
}

View 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());
}

View file

@ -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}`;
}
}

View file

@ -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)
}

View file

@ -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 {

View file

@ -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(())
}

View file

@ -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
View 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())
}
}

View file

@ -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(())