mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-25 16:24:46 +00:00
feat: add attributes support on struct fields and method #3870
Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com>
This commit is contained in:
commit
585bb83e2a
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
@ -50,11 +50,11 @@ jobs:
|
||||
|
||||
- name: Dist
|
||||
if: matrix.os == 'ubuntu-latest' && github.ref == 'refs/heads/release'
|
||||
run: cargo xtask dist --client --version 0.2.$GITHUB_RUN_NUMBER --tag $(date --iso --utc)
|
||||
run: cargo xtask dist --client 0.2.$GITHUB_RUN_NUMBER
|
||||
|
||||
- name: Dist
|
||||
if: matrix.os == 'ubuntu-latest' && github.ref != 'refs/heads/release'
|
||||
run: cargo xtask dist --client --version 0.3.$GITHUB_RUN_NUMBER-nightly --tag nightly
|
||||
run: cargo xtask dist --nightly --client 0.3.$GITHUB_RUN_NUMBER-nightly
|
||||
|
||||
- name: Dist
|
||||
if: matrix.os != 'ubuntu-latest'
|
||||
|
@ -1027,8 +1027,16 @@ impl Type {
|
||||
ty: Ty,
|
||||
) -> Option<Type> {
|
||||
let krate = resolver.krate()?;
|
||||
Some(Type::new_with_resolver_inner(db, krate, resolver, ty))
|
||||
}
|
||||
pub(crate) fn new_with_resolver_inner(
|
||||
db: &dyn HirDatabase,
|
||||
krate: CrateId,
|
||||
resolver: &Resolver,
|
||||
ty: Ty,
|
||||
) -> Type {
|
||||
let environment = TraitEnvironment::lower(db, &resolver);
|
||||
Some(Type { krate, ty: InEnvironment { value: ty, environment } })
|
||||
Type { krate, ty: InEnvironment { value: ty, environment } }
|
||||
}
|
||||
|
||||
fn new(db: &dyn HirDatabase, krate: CrateId, lexical_env: impl HasResolver, ty: Ty) -> Type {
|
||||
@ -1152,27 +1160,6 @@ impl Type {
|
||||
res
|
||||
}
|
||||
|
||||
pub fn variant_fields(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
def: VariantDef,
|
||||
) -> Vec<(StructField, Type)> {
|
||||
// FIXME: check that ty and def match
|
||||
match &self.ty.value {
|
||||
Ty::Apply(a_ty) => {
|
||||
let field_types = db.field_types(def.into());
|
||||
def.fields(db)
|
||||
.into_iter()
|
||||
.map(|it| {
|
||||
let ty = field_types[it.id].clone().subst(&a_ty.parameters);
|
||||
(it, self.derived(ty))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn autoderef<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Type> + 'a {
|
||||
// There should be no inference vars in types passed here
|
||||
// FIXME check that?
|
||||
|
@ -23,7 +23,7 @@ use crate::{
|
||||
semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
|
||||
source_analyzer::{resolve_hir_path, SourceAnalyzer},
|
||||
AssocItem, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, ModuleDef, Name,
|
||||
Origin, Path, ScopeDef, StructField, Trait, Type, TypeParam, VariantDef,
|
||||
Origin, Path, ScopeDef, StructField, Trait, Type, TypeParam,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@ -187,14 +187,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
|
||||
self.analyze(field.syntax()).resolve_record_field(self.db, field)
|
||||
}
|
||||
|
||||
pub fn resolve_record_literal(&self, record_lit: &ast::RecordLit) -> Option<VariantDef> {
|
||||
self.analyze(record_lit.syntax()).resolve_record_literal(self.db, record_lit)
|
||||
}
|
||||
|
||||
pub fn resolve_record_pattern(&self, record_pat: &ast::RecordPat) -> Option<VariantDef> {
|
||||
self.analyze(record_pat.syntax()).resolve_record_pattern(record_pat)
|
||||
}
|
||||
|
||||
pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> {
|
||||
let sa = self.analyze(macro_call.syntax());
|
||||
let macro_call = self.find_file(macro_call.syntax().clone()).with_value(macro_call);
|
||||
@ -212,6 +204,24 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
|
||||
// FIXME: use this instead?
|
||||
// pub fn resolve_name_ref(&self, name_ref: &ast::NameRef) -> Option<???>;
|
||||
|
||||
pub fn record_literal_missing_fields(
|
||||
&self,
|
||||
literal: &ast::RecordLit,
|
||||
) -> Vec<(StructField, Type)> {
|
||||
self.analyze(literal.syntax())
|
||||
.record_literal_missing_fields(self.db, literal)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn record_pattern_missing_fields(
|
||||
&self,
|
||||
pattern: &ast::RecordPat,
|
||||
) -> Vec<(StructField, Type)> {
|
||||
self.analyze(pattern.syntax())
|
||||
.record_pattern_missing_fields(self.db, pattern)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn to_def<T: ToDef>(&self, src: &T) -> Option<T::Def> {
|
||||
let src = self.find_file(src.syntax().clone()).with_value(src).cloned();
|
||||
T::to_def(self, src)
|
||||
|
@ -14,10 +14,13 @@ use hir_def::{
|
||||
},
|
||||
expr::{ExprId, Pat, PatId},
|
||||
resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs},
|
||||
AsMacroCall, DefWithBodyId,
|
||||
AsMacroCall, DefWithBodyId, LocalStructFieldId, StructFieldId, VariantId,
|
||||
};
|
||||
use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile};
|
||||
use hir_ty::InferenceResult;
|
||||
use hir_ty::{
|
||||
expr::{record_literal_missing_fields, record_pattern_missing_fields},
|
||||
InferenceResult, Substs, Ty,
|
||||
};
|
||||
use ra_syntax::{
|
||||
ast::{self, AstNode},
|
||||
SyntaxNode, SyntaxNodePtr, TextUnit,
|
||||
@ -25,8 +28,10 @@ use ra_syntax::{
|
||||
|
||||
use crate::{
|
||||
db::HirDatabase, semantics::PathResolution, Adt, Const, EnumVariant, Function, Local, MacroDef,
|
||||
ModPath, ModuleDef, Path, PathKind, Static, Struct, Trait, Type, TypeAlias, TypeParam,
|
||||
ModPath, ModuleDef, Path, PathKind, Static, Struct, StructField, Trait, Type, TypeAlias,
|
||||
TypeParam,
|
||||
};
|
||||
use ra_db::CrateId;
|
||||
|
||||
/// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of
|
||||
/// original source files. It should not be used inside the HIR itself.
|
||||
@ -164,23 +169,6 @@ impl SourceAnalyzer {
|
||||
Some((struct_field.into(), local))
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_record_literal(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
record_lit: &ast::RecordLit,
|
||||
) -> Option<crate::VariantDef> {
|
||||
let expr_id = self.expr_id(db, &record_lit.clone().into())?;
|
||||
self.infer.as_ref()?.variant_resolution_for_expr(expr_id).map(|it| it.into())
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_record_pattern(
|
||||
&self,
|
||||
record_pat: &ast::RecordPat,
|
||||
) -> Option<crate::VariantDef> {
|
||||
let pat_id = self.pat_id(&record_pat.clone().into())?;
|
||||
self.infer.as_ref()?.variant_resolution_for_pat(pat_id).map(|it| it.into())
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_macro_call(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
@ -231,6 +219,68 @@ impl SourceAnalyzer {
|
||||
resolve_hir_path(db, &self.resolver, &hir_path)
|
||||
}
|
||||
|
||||
pub(crate) fn record_literal_missing_fields(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
literal: &ast::RecordLit,
|
||||
) -> Option<Vec<(StructField, Type)>> {
|
||||
let krate = self.resolver.krate()?;
|
||||
let body = self.body.as_ref()?;
|
||||
let infer = self.infer.as_ref()?;
|
||||
|
||||
let expr_id = self.expr_id(db, &literal.clone().into())?;
|
||||
let substs = match &infer.type_of_expr[expr_id] {
|
||||
Ty::Apply(a_ty) => &a_ty.parameters,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let (variant, missing_fields, _exhaustive) =
|
||||
record_literal_missing_fields(db, infer, expr_id, &body[expr_id])?;
|
||||
let res = self.missing_fields(db, krate, substs, variant, missing_fields);
|
||||
Some(res)
|
||||
}
|
||||
|
||||
pub(crate) fn record_pattern_missing_fields(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
pattern: &ast::RecordPat,
|
||||
) -> Option<Vec<(StructField, Type)>> {
|
||||
let krate = self.resolver.krate()?;
|
||||
let body = self.body.as_ref()?;
|
||||
let infer = self.infer.as_ref()?;
|
||||
|
||||
let pat_id = self.pat_id(&pattern.clone().into())?;
|
||||
let substs = match &infer.type_of_pat[pat_id] {
|
||||
Ty::Apply(a_ty) => &a_ty.parameters,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let (variant, missing_fields) =
|
||||
record_pattern_missing_fields(db, infer, pat_id, &body[pat_id])?;
|
||||
let res = self.missing_fields(db, krate, substs, variant, missing_fields);
|
||||
Some(res)
|
||||
}
|
||||
|
||||
fn missing_fields(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
krate: CrateId,
|
||||
substs: &Substs,
|
||||
variant: VariantId,
|
||||
missing_fields: Vec<LocalStructFieldId>,
|
||||
) -> Vec<(StructField, Type)> {
|
||||
let field_types = db.field_types(variant);
|
||||
|
||||
missing_fields
|
||||
.into_iter()
|
||||
.map(|local_id| {
|
||||
let field = StructFieldId { parent: variant, local_id };
|
||||
let ty = field_types[local_id].clone().subst(substs);
|
||||
(field.into(), Type::new_with_resolver_inner(db, krate, &self.resolver, ty))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn expand(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
|
@ -235,7 +235,10 @@ impl From<PatId> for PatIdOrWild {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct MatchCheckNotImplemented;
|
||||
pub enum MatchCheckErr {
|
||||
NotImplemented,
|
||||
MalformedMatchArm,
|
||||
}
|
||||
|
||||
/// The return type of `is_useful` is either an indication of usefulness
|
||||
/// of the match arm, or an error in the case the match statement
|
||||
@ -244,7 +247,7 @@ pub struct MatchCheckNotImplemented;
|
||||
///
|
||||
/// The `std::result::Result` type is used here rather than a custom enum
|
||||
/// to allow the use of `?`.
|
||||
pub type MatchCheckResult<T> = Result<T, MatchCheckNotImplemented>;
|
||||
pub type MatchCheckResult<T> = Result<T, MatchCheckErr>;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// A row in a Matrix.
|
||||
@ -335,12 +338,12 @@ impl PatStack {
|
||||
Expr::Literal(Literal::Bool(_)) => None,
|
||||
// perhaps this is actually unreachable given we have
|
||||
// already checked that these match arms have the appropriate type?
|
||||
_ => return Err(MatchCheckNotImplemented),
|
||||
_ => return Err(MatchCheckErr::NotImplemented),
|
||||
}
|
||||
}
|
||||
(Pat::Wild, constructor) => Some(self.expand_wildcard(cx, constructor)?),
|
||||
(Pat::Path(_), Constructor::Enum(constructor)) => {
|
||||
// enums with no associated data become `Pat::Path`
|
||||
// unit enum variants become `Pat::Path`
|
||||
let pat_id = self.head().as_id().expect("we know this isn't a wild");
|
||||
if !enum_variant_matches(cx, pat_id, *constructor) {
|
||||
None
|
||||
@ -348,16 +351,23 @@ impl PatStack {
|
||||
Some(self.to_tail())
|
||||
}
|
||||
}
|
||||
(Pat::TupleStruct { args: ref pat_ids, .. }, Constructor::Enum(constructor)) => {
|
||||
(Pat::TupleStruct { args: ref pat_ids, .. }, Constructor::Enum(enum_constructor)) => {
|
||||
let pat_id = self.head().as_id().expect("we know this isn't a wild");
|
||||
if !enum_variant_matches(cx, pat_id, *constructor) {
|
||||
if !enum_variant_matches(cx, pat_id, *enum_constructor) {
|
||||
None
|
||||
} else {
|
||||
// If the enum variant matches, then we need to confirm
|
||||
// that the number of patterns aligns with the expected
|
||||
// number of patterns for that enum variant.
|
||||
if pat_ids.len() != constructor.arity(cx)? {
|
||||
return Err(MatchCheckErr::MalformedMatchArm);
|
||||
}
|
||||
|
||||
Some(self.replace_head_with(pat_ids))
|
||||
}
|
||||
}
|
||||
(Pat::Or(_), _) => return Err(MatchCheckNotImplemented),
|
||||
(_, _) => return Err(MatchCheckNotImplemented),
|
||||
(Pat::Or(_), _) => return Err(MatchCheckErr::NotImplemented),
|
||||
(_, _) => return Err(MatchCheckErr::NotImplemented),
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
@ -514,7 +524,7 @@ pub(crate) fn is_useful(
|
||||
return if any_useful {
|
||||
Ok(Usefulness::Useful)
|
||||
} else if found_unimplemented {
|
||||
Err(MatchCheckNotImplemented)
|
||||
Err(MatchCheckErr::NotImplemented)
|
||||
} else {
|
||||
Ok(Usefulness::NotUseful)
|
||||
};
|
||||
@ -567,7 +577,7 @@ pub(crate) fn is_useful(
|
||||
}
|
||||
|
||||
if found_unimplemented {
|
||||
Err(MatchCheckNotImplemented)
|
||||
Err(MatchCheckErr::NotImplemented)
|
||||
} else {
|
||||
Ok(Usefulness::NotUseful)
|
||||
}
|
||||
@ -604,7 +614,7 @@ impl Constructor {
|
||||
match cx.db.enum_data(e.parent).variants[e.local_id].variant_data.as_ref() {
|
||||
VariantData::Tuple(struct_field_data) => struct_field_data.len(),
|
||||
VariantData::Unit => 0,
|
||||
_ => return Err(MatchCheckNotImplemented),
|
||||
_ => return Err(MatchCheckErr::NotImplemented),
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -637,20 +647,20 @@ fn pat_constructor(cx: &MatchCheckCtx, pat: PatIdOrWild) -> MatchCheckResult<Opt
|
||||
Pat::Tuple(pats) => Some(Constructor::Tuple { arity: pats.len() }),
|
||||
Pat::Lit(lit_expr) => match cx.body.exprs[lit_expr] {
|
||||
Expr::Literal(Literal::Bool(val)) => Some(Constructor::Bool(val)),
|
||||
_ => return Err(MatchCheckNotImplemented),
|
||||
_ => return Err(MatchCheckErr::NotImplemented),
|
||||
},
|
||||
Pat::TupleStruct { .. } | Pat::Path(_) => {
|
||||
let pat_id = pat.as_id().expect("we already know this pattern is not a wild");
|
||||
let variant_id =
|
||||
cx.infer.variant_resolution_for_pat(pat_id).ok_or(MatchCheckNotImplemented)?;
|
||||
cx.infer.variant_resolution_for_pat(pat_id).ok_or(MatchCheckErr::NotImplemented)?;
|
||||
match variant_id {
|
||||
VariantId::EnumVariantId(enum_variant_id) => {
|
||||
Some(Constructor::Enum(enum_variant_id))
|
||||
}
|
||||
_ => return Err(MatchCheckNotImplemented),
|
||||
_ => return Err(MatchCheckErr::NotImplemented),
|
||||
}
|
||||
}
|
||||
_ => return Err(MatchCheckNotImplemented),
|
||||
_ => return Err(MatchCheckErr::NotImplemented),
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
@ -1324,6 +1334,40 @@ mod tests {
|
||||
check_diagnostic(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_match_arm_tuple_missing_pattern() {
|
||||
let content = r"
|
||||
fn test_fn() {
|
||||
match (0) {
|
||||
() => (),
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
// Match arms with the incorrect type are filtered out.
|
||||
check_diagnostic(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_match_arm_tuple_enum_missing_pattern() {
|
||||
let content = r"
|
||||
enum Either {
|
||||
A,
|
||||
B(u32),
|
||||
}
|
||||
fn test_fn() {
|
||||
match Either::A {
|
||||
Either::A => (),
|
||||
Either::B() => (),
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
// We are testing to be sure we don't panic here when the match
|
||||
// arm `Either::B` is missing its pattern.
|
||||
check_no_diagnostic(content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_not_in_scope() {
|
||||
let content = r"
|
||||
|
@ -2,12 +2,8 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use hir_def::{
|
||||
path::{path, Path},
|
||||
resolver::HasResolver,
|
||||
AdtId, FunctionId,
|
||||
};
|
||||
use hir_expand::{diagnostics::DiagnosticSink, name::Name};
|
||||
use hir_def::{path::path, resolver::HasResolver, AdtId, FunctionId};
|
||||
use hir_expand::diagnostics::DiagnosticSink;
|
||||
use ra_syntax::{ast, AstPtr};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
@ -28,7 +24,7 @@ pub use hir_def::{
|
||||
ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, LogicOp,
|
||||
MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, UnaryOp,
|
||||
},
|
||||
VariantId,
|
||||
LocalStructFieldId, VariantId,
|
||||
};
|
||||
|
||||
pub struct ExprValidator<'a, 'b: 'a> {
|
||||
@ -49,14 +45,37 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
|
||||
pub fn validate_body(&mut self, db: &dyn HirDatabase) {
|
||||
let body = db.body(self.func.into());
|
||||
|
||||
for e in body.exprs.iter() {
|
||||
if let (id, Expr::RecordLit { path, fields, spread }) = e {
|
||||
self.validate_record_literal(id, path, fields, *spread, db);
|
||||
} else if let (id, Expr::Match { expr, arms }) = e {
|
||||
for (id, expr) in body.exprs.iter() {
|
||||
if let Some((variant_def, missed_fields, true)) =
|
||||
record_literal_missing_fields(db, &self.infer, id, expr)
|
||||
{
|
||||
// XXX: only look at source_map if we do have missing fields
|
||||
let (_, source_map) = db.body_with_source_map(self.func.into());
|
||||
|
||||
if let Ok(source_ptr) = source_map.expr_syntax(id) {
|
||||
if let Some(expr) = source_ptr.value.left() {
|
||||
let root = source_ptr.file_syntax(db.upcast());
|
||||
if let ast::Expr::RecordLit(record_lit) = expr.to_node(&root) {
|
||||
if let Some(field_list) = record_lit.record_field_list() {
|
||||
let variant_data = variant_data(db.upcast(), variant_def);
|
||||
let missed_fields = missed_fields
|
||||
.into_iter()
|
||||
.map(|idx| variant_data.fields()[idx].name.clone())
|
||||
.collect();
|
||||
self.sink.push(MissingFields {
|
||||
file: source_ptr.file_id,
|
||||
field_list: AstPtr::new(&field_list),
|
||||
missed_fields,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Expr::Match { expr, arms } = expr {
|
||||
self.validate_match(id, *expr, arms, db, self.infer.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let body_expr = &body[body.body_expr];
|
||||
if let Expr::Block { tail: Some(t), .. } = body_expr {
|
||||
self.validate_results_in_tail_expr(body.body_expr, *t, db);
|
||||
@ -145,61 +164,6 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_record_literal(
|
||||
&mut self,
|
||||
id: ExprId,
|
||||
_path: &Option<Path>,
|
||||
fields: &[RecordLitField],
|
||||
spread: Option<ExprId>,
|
||||
db: &dyn HirDatabase,
|
||||
) {
|
||||
if spread.is_some() {
|
||||
return;
|
||||
};
|
||||
let variant_def: VariantId = match self.infer.variant_resolution_for_expr(id) {
|
||||
Some(VariantId::UnionId(_)) | None => return,
|
||||
Some(it) => it,
|
||||
};
|
||||
if let VariantId::UnionId(_) = variant_def {
|
||||
return;
|
||||
}
|
||||
|
||||
let variant_data = variant_data(db.upcast(), variant_def);
|
||||
|
||||
let lit_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
|
||||
let missed_fields: Vec<Name> = variant_data
|
||||
.fields()
|
||||
.iter()
|
||||
.filter_map(|(_f, d)| {
|
||||
let name = d.name.clone();
|
||||
if lit_fields.contains(&name) {
|
||||
None
|
||||
} else {
|
||||
Some(name)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if missed_fields.is_empty() {
|
||||
return;
|
||||
}
|
||||
let (_, source_map) = db.body_with_source_map(self.func.into());
|
||||
|
||||
if let Ok(source_ptr) = source_map.expr_syntax(id) {
|
||||
if let Some(expr) = source_ptr.value.left() {
|
||||
let root = source_ptr.file_syntax(db.upcast());
|
||||
if let ast::Expr::RecordLit(record_lit) = expr.to_node(&root) {
|
||||
if let Some(field_list) = record_lit.record_field_list() {
|
||||
self.sink.push(MissingFields {
|
||||
file: source_ptr.file_id,
|
||||
field_list: AstPtr::new(&field_list),
|
||||
missed_fields,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_results_in_tail_expr(&mut self, body_id: ExprId, id: ExprId, db: &dyn HirDatabase) {
|
||||
// the mismatch will be on the whole block currently
|
||||
let mismatch = match self.infer.type_mismatch_for_expr(body_id) {
|
||||
@ -232,3 +196,63 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_literal_missing_fields(
|
||||
db: &dyn HirDatabase,
|
||||
infer: &InferenceResult,
|
||||
id: ExprId,
|
||||
expr: &Expr,
|
||||
) -> Option<(VariantId, Vec<LocalStructFieldId>, /*exhaustive*/ bool)> {
|
||||
let (fields, exhausitve) = match expr {
|
||||
Expr::RecordLit { path: _, fields, spread } => (fields, spread.is_none()),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let variant_def = infer.variant_resolution_for_expr(id)?;
|
||||
if let VariantId::UnionId(_) = variant_def {
|
||||
return None;
|
||||
}
|
||||
|
||||
let variant_data = variant_data(db.upcast(), variant_def);
|
||||
|
||||
let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
|
||||
let missed_fields: Vec<LocalStructFieldId> = variant_data
|
||||
.fields()
|
||||
.iter()
|
||||
.filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
|
||||
.collect();
|
||||
if missed_fields.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some((variant_def, missed_fields, exhausitve))
|
||||
}
|
||||
|
||||
pub fn record_pattern_missing_fields(
|
||||
db: &dyn HirDatabase,
|
||||
infer: &InferenceResult,
|
||||
id: PatId,
|
||||
pat: &Pat,
|
||||
) -> Option<(VariantId, Vec<LocalStructFieldId>)> {
|
||||
let fields = match pat {
|
||||
Pat::Record { path: _, args } => args,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let variant_def = infer.variant_resolution_for_pat(id)?;
|
||||
if let VariantId::UnionId(_) = variant_def {
|
||||
return None;
|
||||
}
|
||||
|
||||
let variant_data = variant_data(db.upcast(), variant_def);
|
||||
|
||||
let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
|
||||
let missed_fields: Vec<LocalStructFieldId> = variant_data
|
||||
.fields()
|
||||
.iter()
|
||||
.filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
|
||||
.collect();
|
||||
if missed_fields.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some((variant_def, missed_fields))
|
||||
}
|
||||
|
@ -336,7 +336,7 @@ fn no_such_field_with_feature_flag_diagnostics() {
|
||||
pub(crate) fn new(my_val: usize, bar: bool) -> Self {
|
||||
Self { my_val, bar }
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(feature = "foo"))]
|
||||
pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
|
||||
Self { my_val }
|
||||
|
@ -1,65 +1,24 @@
|
||||
//! Complete fields in record literals and patterns.
|
||||
use ra_syntax::{ast, ast::NameOwner, SmolStr};
|
||||
|
||||
use crate::completion::{CompletionContext, Completions};
|
||||
|
||||
pub(super) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
|
||||
let (ty, variant, already_present_fields) =
|
||||
match (ctx.record_lit_pat.as_ref(), ctx.record_lit_syntax.as_ref()) {
|
||||
(None, None) => return None,
|
||||
(Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"),
|
||||
(Some(record_pat), _) => (
|
||||
ctx.sema.type_of_pat(&record_pat.clone().into())?,
|
||||
ctx.sema.resolve_record_pattern(record_pat)?,
|
||||
pattern_ascribed_fields(record_pat),
|
||||
),
|
||||
(_, Some(record_lit)) => (
|
||||
ctx.sema.type_of_expr(&record_lit.clone().into())?,
|
||||
ctx.sema.resolve_record_literal(record_lit)?,
|
||||
literal_ascribed_fields(record_lit),
|
||||
),
|
||||
};
|
||||
let missing_fields = match (ctx.record_lit_pat.as_ref(), ctx.record_lit_syntax.as_ref()) {
|
||||
(None, None) => return None,
|
||||
(Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"),
|
||||
(Some(record_pat), _) => ctx.sema.record_pattern_missing_fields(record_pat),
|
||||
(_, Some(record_lit)) => ctx.sema.record_literal_missing_fields(record_lit),
|
||||
};
|
||||
|
||||
for (field, field_ty) in ty.variant_fields(ctx.db, variant).into_iter().filter(|(field, _)| {
|
||||
// FIXME: already_present_names better be `Vec<hir::Name>`
|
||||
!already_present_fields.contains(&SmolStr::from(field.name(ctx.db).to_string()))
|
||||
}) {
|
||||
acc.add_field(ctx, field, &field_ty);
|
||||
for (field, ty) in missing_fields {
|
||||
acc.add_field(ctx, field, &ty)
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn literal_ascribed_fields(record_lit: &ast::RecordLit) -> Vec<SmolStr> {
|
||||
record_lit
|
||||
.record_field_list()
|
||||
.map(|field_list| field_list.fields())
|
||||
.map(|fields| {
|
||||
fields
|
||||
.into_iter()
|
||||
.filter_map(|field| field.name_ref())
|
||||
.map(|name_ref| name_ref.text().clone())
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn pattern_ascribed_fields(record_pat: &ast::RecordPat) -> Vec<SmolStr> {
|
||||
record_pat
|
||||
.record_field_pat_list()
|
||||
.map(|pat_list| {
|
||||
pat_list
|
||||
.record_field_pats()
|
||||
.filter_map(|fild_pat| fild_pat.name())
|
||||
.chain(pat_list.bind_pats().filter_map(|bind_pat| bind_pat.name()))
|
||||
.map(|name| name.text().clone())
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod record_lit_tests {
|
||||
mod record_pat_tests {
|
||||
use insta::assert_debug_snapshot;
|
||||
|
||||
use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
|
||||
@ -205,7 +164,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
mod record_pat_tests {
|
||||
mod record_lit_tests {
|
||||
use insta::assert_debug_snapshot;
|
||||
|
||||
use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
|
||||
@ -410,5 +369,38 @@ mod tests {
|
||||
]
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_functional_update() {
|
||||
let completions = complete(
|
||||
r"
|
||||
struct S {
|
||||
foo1: u32,
|
||||
foo2: u32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let foo1 = 1;
|
||||
let s = S {
|
||||
foo1,
|
||||
<|>
|
||||
.. loop {}
|
||||
}
|
||||
}
|
||||
",
|
||||
);
|
||||
assert_debug_snapshot!(completions, @r###"
|
||||
[
|
||||
CompletionItem {
|
||||
label: "foo2",
|
||||
source_range: [221; 221),
|
||||
delete: [221; 221),
|
||||
insert: "foo2",
|
||||
kind: Field,
|
||||
detail: "u32",
|
||||
},
|
||||
]
|
||||
"###);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
39
crates/ra_ide/src/snapshots/highlight_injection.html
Normal file
39
crates/ra_ide/src/snapshots/highlight_injection.html
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
<style>
|
||||
body { margin: 0; }
|
||||
pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
|
||||
|
||||
.lifetime { color: #DFAF8F; font-style: italic; }
|
||||
.comment { color: #7F9F7F; }
|
||||
.struct, .enum { color: #7CB8BB; }
|
||||
.enum_variant { color: #BDE0F3; }
|
||||
.string_literal { color: #CC9393; }
|
||||
.field { color: #94BFF3; }
|
||||
.function { color: #93E0E3; }
|
||||
.parameter { color: #94BFF3; }
|
||||
.text { color: #DCDCCC; }
|
||||
.type { color: #7CB8BB; }
|
||||
.builtin_type { color: #8CD0D3; }
|
||||
.type_param { color: #DFAF8F; }
|
||||
.attribute { color: #94BFF3; }
|
||||
.numeric_literal { color: #BFEBBF; }
|
||||
.macro { color: #94BFF3; }
|
||||
.module { color: #AFD8AF; }
|
||||
.variable { color: #DCDCCC; }
|
||||
.mutable { text-decoration: underline; }
|
||||
|
||||
.keyword { color: #F0DFAF; font-weight: bold; }
|
||||
.keyword.unsafe { color: #BC8383; font-weight: bold; }
|
||||
.control { font-style: italic; }
|
||||
</style>
|
||||
<pre><code><span class="keyword">fn</span> <span class="function declaration">fixture</span>(<span class="variable declaration">ra_fixture</span>: &<span class="builtin_type">str</span>) {}
|
||||
|
||||
<span class="keyword">fn</span> <span class="function declaration">main</span>() {
|
||||
<span class="function">fixture</span>(<span class="string_literal">r#"</span>
|
||||
<span class="keyword">trait</span> <span class="trait declaration">Foo</span> {
|
||||
<span class="keyword">fn</span> <span class="function declaration">foo</span>() {
|
||||
<span class="macro">println!</span>(<span class="string_literal">"2 + 2 = {}"</span>, <span class="numeric_literal">4</span>);
|
||||
}
|
||||
}<span class="string_literal">"#</span>
|
||||
);
|
||||
}</code></pre>
|
@ -26,7 +26,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
||||
.keyword.unsafe { color: #BC8383; font-weight: bold; }
|
||||
.control { font-style: italic; }
|
||||
</style>
|
||||
<pre><code><span class="attribute">#</span><span class="attribute">[</span><span class="attribute">derive</span><span class="attribute">(</span><span class="attribute">Clone</span><span class="attribute">,</span><span class="attribute"> </span><span class="attribute">Debug</span><span class="attribute">)</span><span class="attribute">]</span>
|
||||
<pre><code><span class="attribute">#[derive(Clone, Debug)]</span>
|
||||
<span class="keyword">struct</span> <span class="struct declaration">Foo</span> {
|
||||
<span class="keyword">pub</span> <span class="field declaration">x</span>: <span class="builtin_type">i32</span>,
|
||||
<span class="keyword">pub</span> <span class="field declaration">y</span>: <span class="builtin_type">i32</span>,
|
||||
@ -36,11 +36,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
||||
<span class="function">foo</span>::<<span class="lifetime">'a</span>, <span class="builtin_type">i32</span>>()
|
||||
}
|
||||
|
||||
<span class="macro">macro_rules</span><span class="macro">!</span> def_fn {
|
||||
<span class="macro">macro_rules!</span> def_fn {
|
||||
($($tt:tt)*) => {$($tt)*}
|
||||
}
|
||||
|
||||
<span class="macro">def_fn</span><span class="macro">!</span> {
|
||||
<span class="macro">def_fn!</span> {
|
||||
<span class="keyword">fn</span> <span class="function declaration">bar</span>() -> <span class="builtin_type">u32</span> {
|
||||
<span class="numeric_literal">100</span>
|
||||
}
|
||||
@ -48,7 +48,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
||||
|
||||
<span class="comment">// comment</span>
|
||||
<span class="keyword">fn</span> <span class="function declaration">main</span>() {
|
||||
<span class="macro">println</span><span class="macro">!</span>(<span class="string_literal">"Hello, {}!"</span>, <span class="numeric_literal">92</span>);
|
||||
<span class="macro">println!</span>(<span class="string_literal">"Hello, {}!"</span>, <span class="numeric_literal">92</span>);
|
||||
|
||||
<span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">vec</span> = Vec::new();
|
||||
<span class="keyword control">if</span> <span class="keyword">true</span> {
|
||||
@ -73,7 +73,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
||||
<span class="keyword">impl</span><<span class="type_param declaration">T</span>> <span class="enum">Option</span><<span class="type_param">T</span>> {
|
||||
<span class="keyword">fn</span> <span class="function declaration">and</span><<span class="type_param declaration">U</span>>(<span class="keyword">self</span>, <span class="variable declaration">other</span>: <span class="enum">Option</span><<span class="type_param">U</span>>) -> <span class="enum">Option</span><(<span class="type_param">T</span>, <span class="type_param">U</span>)> {
|
||||
<span class="keyword control">match</span> <span class="variable">other</span> {
|
||||
<span class="enum_variant">None</span> => <span class="macro">unimplemented</span><span class="macro">!</span>(),
|
||||
<span class="enum_variant">None</span> => <span class="macro">unimplemented!</span>(),
|
||||
<span class="variable declaration">Nope</span> => <span class="variable">Nope</span>,
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ use crate::{call_info::call_info_for_token, Analysis, FileId};
|
||||
pub(crate) use html::highlight_as_html;
|
||||
pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HighlightedRange {
|
||||
pub range: TextRange,
|
||||
pub highlight: Highlight,
|
||||
@ -55,13 +55,55 @@ pub(crate) fn highlight(
|
||||
};
|
||||
|
||||
let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
|
||||
let mut res = Vec::new();
|
||||
// We use a stack for the DFS traversal below.
|
||||
// When we leave a node, the we use it to flatten the highlighted ranges.
|
||||
let mut res: Vec<Vec<HighlightedRange>> = vec![Vec::new()];
|
||||
|
||||
let mut current_macro_call: Option<ast::MacroCall> = None;
|
||||
|
||||
// Walk all nodes, keeping track of whether we are inside a macro or not.
|
||||
// If in macro, expand it first and highlight the expanded code.
|
||||
for event in root.preorder_with_tokens() {
|
||||
match &event {
|
||||
WalkEvent::Enter(_) => res.push(Vec::new()),
|
||||
WalkEvent::Leave(_) => {
|
||||
/* Flattens the highlighted ranges.
|
||||
*
|
||||
* For example `#[cfg(feature = "foo")]` contains the nested ranges:
|
||||
* 1) parent-range: Attribute [0, 23)
|
||||
* 2) child-range: String [16, 21)
|
||||
*
|
||||
* The following code implements the flattening, for our example this results to:
|
||||
* `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
|
||||
*/
|
||||
let children = res.pop().unwrap();
|
||||
let prev = res.last_mut().unwrap();
|
||||
let needs_flattening = !children.is_empty()
|
||||
&& !prev.is_empty()
|
||||
&& children.first().unwrap().range.is_subrange(&prev.last().unwrap().range);
|
||||
if !needs_flattening {
|
||||
prev.extend(children);
|
||||
} else {
|
||||
let mut parent = prev.pop().unwrap();
|
||||
for ele in children {
|
||||
assert!(ele.range.is_subrange(&parent.range));
|
||||
let mut cloned = parent.clone();
|
||||
parent.range = TextRange::from_to(parent.range.start(), ele.range.start());
|
||||
cloned.range = TextRange::from_to(ele.range.end(), cloned.range.end());
|
||||
if !parent.range.is_empty() {
|
||||
prev.push(parent);
|
||||
}
|
||||
prev.push(ele);
|
||||
parent = cloned;
|
||||
}
|
||||
if !parent.range.is_empty() {
|
||||
prev.push(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let current = res.last_mut().expect("during DFS traversal, the stack must not be empty");
|
||||
|
||||
let event_range = match &event {
|
||||
WalkEvent::Enter(it) => it.text_range(),
|
||||
WalkEvent::Leave(it) => it.text_range(),
|
||||
@ -77,7 +119,7 @@ pub(crate) fn highlight(
|
||||
WalkEvent::Enter(Some(mc)) => {
|
||||
current_macro_call = Some(mc.clone());
|
||||
if let Some(range) = macro_call_range(&mc) {
|
||||
res.push(HighlightedRange {
|
||||
current.push(HighlightedRange {
|
||||
range,
|
||||
highlight: HighlightTag::Macro.into(),
|
||||
binding_hash: None,
|
||||
@ -119,7 +161,7 @@ pub(crate) fn highlight(
|
||||
|
||||
if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) {
|
||||
let expanded = element_to_highlight.as_token().unwrap().clone();
|
||||
if highlight_injection(&mut res, &sema, token, expanded).is_some() {
|
||||
if highlight_injection(current, &sema, token, expanded).is_some() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -127,10 +169,17 @@ pub(crate) fn highlight(
|
||||
if let Some((highlight, binding_hash)) =
|
||||
highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight)
|
||||
{
|
||||
res.push(HighlightedRange { range, highlight, binding_hash });
|
||||
current.push(HighlightedRange { range, highlight, binding_hash });
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(res.len(), 1, "after DFS traversal, the stack should only contain a single element");
|
||||
let res = res.pop().unwrap();
|
||||
// Check that ranges are sorted and disjoint
|
||||
assert!(res
|
||||
.iter()
|
||||
.zip(res.iter().skip(1))
|
||||
.all(|(left, right)| left.range.end() <= right.range.start()));
|
||||
res
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
//! Renders a bit of code as HTML.
|
||||
|
||||
use ra_db::SourceDatabase;
|
||||
use ra_syntax::AstNode;
|
||||
use ra_syntax::{AstNode, TextUnit};
|
||||
|
||||
use crate::{FileId, HighlightedRange, RootDatabase};
|
||||
use crate::{FileId, RootDatabase};
|
||||
|
||||
use super::highlight;
|
||||
|
||||
@ -21,51 +21,35 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo
|
||||
)
|
||||
}
|
||||
|
||||
let mut ranges = highlight(db, file_id, None);
|
||||
ranges.sort_by_key(|it| it.range.start());
|
||||
// quick non-optimal heuristic to intersect token ranges and highlighted ranges
|
||||
let mut frontier = 0;
|
||||
let mut could_intersect: Vec<&HighlightedRange> = Vec::new();
|
||||
|
||||
let ranges = highlight(db, file_id, None);
|
||||
let text = parse.tree().syntax().to_string();
|
||||
let mut prev_pos = TextUnit::from(0);
|
||||
let mut buf = String::new();
|
||||
buf.push_str(&STYLE);
|
||||
buf.push_str("<pre><code>");
|
||||
let tokens = parse.tree().syntax().descendants_with_tokens().filter_map(|it| it.into_token());
|
||||
for token in tokens {
|
||||
could_intersect.retain(|it| token.text_range().start() <= it.range.end());
|
||||
while let Some(r) = ranges.get(frontier) {
|
||||
if r.range.start() <= token.text_range().end() {
|
||||
could_intersect.push(r);
|
||||
frontier += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let text = html_escape(&token.text());
|
||||
let ranges = could_intersect
|
||||
.iter()
|
||||
.filter(|it| token.text_range().is_subrange(&it.range))
|
||||
.collect::<Vec<_>>();
|
||||
if ranges.is_empty() {
|
||||
for range in &ranges {
|
||||
if range.range.start() > prev_pos {
|
||||
let curr = &text[prev_pos.to_usize()..range.range.start().to_usize()];
|
||||
let text = html_escape(curr);
|
||||
buf.push_str(&text);
|
||||
} else {
|
||||
let classes = ranges
|
||||
.iter()
|
||||
.map(|it| it.highlight.to_string().replace('.', " "))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
let binding_hash = ranges.first().and_then(|x| x.binding_hash);
|
||||
let color = match (rainbow, binding_hash) {
|
||||
(true, Some(hash)) => format!(
|
||||
" data-binding-hash=\"{}\" style=\"color: {};\"",
|
||||
hash,
|
||||
rainbowify(hash)
|
||||
),
|
||||
_ => "".into(),
|
||||
};
|
||||
buf.push_str(&format!("<span class=\"{}\"{}>{}</span>", classes, color, text));
|
||||
}
|
||||
let curr = &text[range.range.start().to_usize()..range.range.end().to_usize()];
|
||||
|
||||
let class = range.highlight.to_string().replace('.', " ");
|
||||
let color = match (rainbow, range.binding_hash) {
|
||||
(true, Some(hash)) => {
|
||||
format!(" data-binding-hash=\"{}\" style=\"color: {};\"", hash, rainbowify(hash))
|
||||
}
|
||||
_ => "".into(),
|
||||
};
|
||||
buf.push_str(&format!("<span class=\"{}\"{}>{}</span>", class, color, html_escape(curr)));
|
||||
|
||||
prev_pos = range.range.end();
|
||||
}
|
||||
// Add the remaining (non-highlighted) text
|
||||
let curr = &text[prev_pos.to_usize()..];
|
||||
let text = html_escape(curr);
|
||||
buf.push_str(&text);
|
||||
buf.push_str("</code></pre>");
|
||||
buf
|
||||
}
|
||||
|
@ -131,3 +131,28 @@ fn test_ranges() {
|
||||
|
||||
assert_eq!(&highlights[0].highlight.to_string(), "field.declaration");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_flattening() {
|
||||
let (analysis, file_id) = single_file(
|
||||
r##"
|
||||
fn fixture(ra_fixture: &str) {}
|
||||
|
||||
fn main() {
|
||||
fixture(r#"
|
||||
trait Foo {
|
||||
fn foo() {
|
||||
println!("2 + 2 = {}", 4);
|
||||
}
|
||||
}"#
|
||||
);
|
||||
}"##
|
||||
.trim(),
|
||||
);
|
||||
|
||||
let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_injection.html");
|
||||
let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
|
||||
let expected_html = &read_text(&dst_file);
|
||||
fs::write(dst_file, &actual_html).unwrap();
|
||||
assert_eq_text!(expected_html, actual_html);
|
||||
}
|
||||
|
@ -1614,6 +1614,23 @@ fn test_issue_2520() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_issue_3861() {
|
||||
let macro_fixture = parse_macro(
|
||||
r#"
|
||||
macro_rules! rgb_color {
|
||||
($p:expr, $t: ty) => {
|
||||
pub fn new() {
|
||||
let _ = 0 as $t << $p;
|
||||
}
|
||||
};
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
macro_fixture.expand_items(r#"rgb_color!(8 + 8, u32);"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_repeat_bad_var() {
|
||||
// FIXME: the second rule of the macro should be removed and an error about
|
||||
|
@ -7,7 +7,7 @@ pub(super) const TYPE_FIRST: TokenSet = paths::PATH_FIRST.union(token_set![
|
||||
DYN_KW, L_ANGLE,
|
||||
]);
|
||||
|
||||
const TYPE_RECOVERY_SET: TokenSet = token_set![R_PAREN, COMMA];
|
||||
const TYPE_RECOVERY_SET: TokenSet = token_set![R_PAREN, COMMA, L_DOLLAR];
|
||||
|
||||
pub(crate) fn type_(p: &mut Parser) {
|
||||
type_with_bounds_cond(p, true);
|
||||
|
@ -54,12 +54,14 @@ pub struct Diagnostic {
|
||||
}
|
||||
|
||||
macro_rules! diagnostic_child_methods {
|
||||
($spanned:ident, $regular:ident, $level:expr) => (
|
||||
($spanned:ident, $regular:ident, $level:expr) => {
|
||||
/// Adds a new child diagnostic message to `self` with the level
|
||||
/// identified by this method's name with the given `spans` and
|
||||
/// `message`.
|
||||
pub fn $spanned<S, T>(mut self, spans: S, message: T) -> Diagnostic
|
||||
where S: MultiSpan, T: Into<String>
|
||||
where
|
||||
S: MultiSpan,
|
||||
T: Into<String>,
|
||||
{
|
||||
self.children.push(Diagnostic::spanned(spans, $level, message));
|
||||
self
|
||||
@ -71,7 +73,7 @@ macro_rules! diagnostic_child_methods {
|
||||
self.children.push(Diagnostic::new($level, message));
|
||||
self
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
/// Iterator over the children diagnostics of a `Diagnostic`.
|
||||
|
@ -169,13 +169,13 @@ pub mod token_stream {
|
||||
pub struct Span(bridge::client::Span);
|
||||
|
||||
macro_rules! diagnostic_method {
|
||||
($name:ident, $level:expr) => (
|
||||
($name:ident, $level:expr) => {
|
||||
/// Creates a new `Diagnostic` with the given `message` at the span
|
||||
/// `self`.
|
||||
pub fn $name<T: Into<String>>(self, message: T) -> Diagnostic {
|
||||
Diagnostic::spanned(self, $level, message)
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
impl Span {
|
||||
|
@ -316,7 +316,7 @@ impl<'a> SyntaxRewriter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ops::AddAssign for SyntaxRewriter<'_> {
|
||||
impl ops::AddAssign for SyntaxRewriter<'_> {
|
||||
fn add_assign(&mut self, rhs: SyntaxRewriter) {
|
||||
assert!(rhs.f.is_none());
|
||||
self.replacements.extend(rhs.replacements)
|
||||
|
@ -187,30 +187,7 @@ 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": ["rust-analyzer"],
|
||||
"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)
|
||||
Invoke the command palette (`ctrl+shift+p`) 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
|
||||
|
||||
|
44
editors/code/package-lock.json
generated
44
editors/code/package-lock.json
generated
@ -115,25 +115,25 @@
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "2.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.26.0.tgz",
|
||||
"integrity": "sha512-4yUnLv40bzfzsXcTAtZyTjbiGUXMrcIJcIMioI22tSOyAxpdXiZ4r7YQUU8Jj6XXrLz9d5aMHPQf5JFR7h27Nw==",
|
||||
"version": "2.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.27.0.tgz",
|
||||
"integrity": "sha512-/my+vVHRN7zYgcp0n4z5A6HAK7bvKGBiswaM5zIlOQczsxj/aiD7RcgD+dvVFuwFaGh5+kM7XA6Q6PN0bvb1tw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/experimental-utils": "2.26.0",
|
||||
"@typescript-eslint/experimental-utils": "2.27.0",
|
||||
"functional-red-black-tree": "^1.0.1",
|
||||
"regexpp": "^3.0.0",
|
||||
"tsutils": "^3.17.1"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/experimental-utils": {
|
||||
"version": "2.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.26.0.tgz",
|
||||
"integrity": "sha512-RELVoH5EYd+JlGprEyojUv9HeKcZqF7nZUGSblyAw1FwOGNnmQIU8kxJ69fttQvEwCsX5D6ECJT8GTozxrDKVQ==",
|
||||
"version": "2.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.27.0.tgz",
|
||||
"integrity": "sha512-vOsYzjwJlY6E0NJRXPTeCGqjv5OHgRU1kzxHKWJVPjDYGbPgLudBXjIlc+OD1hDBZ4l1DLbOc5VjofKahsu9Jw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.3",
|
||||
"@typescript-eslint/typescript-estree": "2.26.0",
|
||||
"@typescript-eslint/typescript-estree": "2.27.0",
|
||||
"eslint-scope": "^5.0.0",
|
||||
"eslint-utils": "^2.0.0"
|
||||
},
|
||||
@ -150,21 +150,21 @@
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/parser": {
|
||||
"version": "2.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.26.0.tgz",
|
||||
"integrity": "sha512-+Xj5fucDtdKEVGSh9353wcnseMRkPpEAOY96EEenN7kJVrLqy/EVwtIh3mxcUz8lsFXW1mT5nN5vvEam/a5HiQ==",
|
||||
"version": "2.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.27.0.tgz",
|
||||
"integrity": "sha512-HFUXZY+EdwrJXZo31DW4IS1ujQW3krzlRjBrFRrJcMDh0zCu107/nRfhk/uBasO8m0NVDbBF5WZKcIUMRO7vPg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/eslint-visitor-keys": "^1.0.0",
|
||||
"@typescript-eslint/experimental-utils": "2.26.0",
|
||||
"@typescript-eslint/typescript-estree": "2.26.0",
|
||||
"@typescript-eslint/experimental-utils": "2.27.0",
|
||||
"@typescript-eslint/typescript-estree": "2.27.0",
|
||||
"eslint-visitor-keys": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "2.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.26.0.tgz",
|
||||
"integrity": "sha512-3x4SyZCLB4zsKsjuhxDLeVJN6W29VwBnYpCsZ7vIdPel9ZqLfIZJgJXO47MNUkurGpQuIBALdPQKtsSnWpE1Yg==",
|
||||
"version": "2.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.27.0.tgz",
|
||||
"integrity": "sha512-t2miCCJIb/FU8yArjAvxllxbTiyNqaXJag7UOpB5DVoM3+xnjeOngtqlJkLRnMtzaRcJhe3CIR9RmL40omubhg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "^4.1.1",
|
||||
@ -1367,9 +1367,9 @@
|
||||
}
|
||||
},
|
||||
"regexpp": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz",
|
||||
"integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
|
||||
"integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"resolve": {
|
||||
@ -1407,9 +1407,9 @@
|
||||
}
|
||||
},
|
||||
"rollup": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.3.2.tgz",
|
||||
"integrity": "sha512-p66+fbfaUUOGE84sHXAOgfeaYQMslgAazoQMp//nlR519R61213EPFgrMZa48j31jNacJwexSAR1Q8V/BwGKBA==",
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.3.3.tgz",
|
||||
"integrity": "sha512-uJ9VNWk80mb4wDCSfd1AyHoSc9TrWbkZtnO6wbsMTp9muSWkT26Dvc99MX1yGCOTvUN1Skw/KpFzKdUDuZKTXA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fsevents": "~2.1.2"
|
||||
|
@ -21,7 +21,7 @@
|
||||
"Programming Languages"
|
||||
],
|
||||
"engines": {
|
||||
"vscode": "^1.43.0"
|
||||
"vscode": "^1.44.0"
|
||||
},
|
||||
"enableProposedApi": true,
|
||||
"scripts": {
|
||||
@ -42,10 +42,10 @@
|
||||
"@types/node": "^12.12.34",
|
||||
"@types/node-fetch": "^2.5.5",
|
||||
"@types/vscode": "^1.43.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||
"@typescript-eslint/parser": "^2.26.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.27.0",
|
||||
"@typescript-eslint/parser": "^2.27.0",
|
||||
"eslint": "^6.8.0",
|
||||
"rollup": "^2.3.2",
|
||||
"rollup": "^2.3.3",
|
||||
"tslib": "^1.11.1",
|
||||
"typescript": "^3.8.3",
|
||||
"typescript-formatter": "^7.2.2",
|
||||
@ -342,11 +342,6 @@
|
||||
"default": true,
|
||||
"description": "Show function name and docs in parameter hints"
|
||||
},
|
||||
"rust-analyzer.highlighting.semanticTokens": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Use proposed semantic tokens API for syntax highlighting"
|
||||
},
|
||||
"rust-analyzer.updates.channel": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
@ -1,11 +1,10 @@
|
||||
import * as lc from 'vscode-languageclient';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { Config } from './config';
|
||||
import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed';
|
||||
import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed';
|
||||
|
||||
export async function createClient(config: Config, serverPath: string, cwd: string): Promise<lc.LanguageClient> {
|
||||
export async function createClient(serverPath: string, cwd: string): Promise<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.
|
||||
@ -73,15 +72,12 @@ export async function createClient(config: Config, serverPath: string, cwd: stri
|
||||
};
|
||||
|
||||
// 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.
|
||||
// Here we want to enable CallHierarchyFeature and SemanticTokensFeature
|
||||
// since they are available on stable.
|
||||
// Note that while these features are stable in vscode their LSP protocol
|
||||
// implementations are still in the "proposed" category for 3.16.
|
||||
res.registerFeature(new CallHierarchyFeature(res));
|
||||
|
||||
if (config.package.enableProposedApi) {
|
||||
if (config.highlightingSemanticTokens) {
|
||||
res.registerFeature(new SemanticTokensFeature(res));
|
||||
}
|
||||
}
|
||||
res.registerFeature(new SemanticTokensFeature(res));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
@ -69,7 +69,6 @@ export class Config {
|
||||
get serverPath() { return this.cfg.get<null | string>("serverPath")!; }
|
||||
get channel() { return this.cfg.get<UpdatesChannel>("updates.channel")!; }
|
||||
get askBeforeDownload() { return this.cfg.get<boolean>("updates.askBeforeDownload")!; }
|
||||
get highlightingSemanticTokens() { return this.cfg.get<boolean>("highlighting.semanticTokens")!; }
|
||||
get traceExtension() { return this.cfg.get<boolean>("trace.extension")!; }
|
||||
|
||||
get inlayHints() {
|
||||
|
@ -21,7 +21,7 @@ export class Ctx {
|
||||
serverPath: string,
|
||||
cwd: string,
|
||||
): Promise<Ctx> {
|
||||
const client = await createClient(config, serverPath, cwd);
|
||||
const client = await createClient(serverPath, cwd);
|
||||
const res = new Ctx(config, extCtx, client, serverPath);
|
||||
res.pushCleanup(client.start());
|
||||
await client.onReady();
|
||||
|
@ -3,24 +3,20 @@ use std::path::PathBuf;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
not_bash::{fs2, pushd, rm_rf, run},
|
||||
not_bash::{date_iso, fs2, pushd, rm_rf, run},
|
||||
project_root,
|
||||
};
|
||||
|
||||
pub struct ClientOpts {
|
||||
pub version: String,
|
||||
pub release_tag: String,
|
||||
}
|
||||
|
||||
pub fn run_dist(client_opts: Option<ClientOpts>) -> Result<()> {
|
||||
pub fn run_dist(nightly: bool, client_version: Option<String>) -> Result<()> {
|
||||
let dist = project_root().join("dist");
|
||||
rm_rf(&dist)?;
|
||||
fs2::create_dir_all(&dist)?;
|
||||
|
||||
if let Some(ClientOpts { version, release_tag }) = client_opts {
|
||||
if let Some(version) = client_version {
|
||||
let release_tag = if nightly { "nightly".to_string() } else { date_iso()? };
|
||||
dist_client(&version, &release_tag)?;
|
||||
}
|
||||
dist_server()?;
|
||||
dist_server(nightly)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -50,7 +46,7 @@ fn dist_client(version: &str, release_tag: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dist_server() -> Result<()> {
|
||||
fn dist_server(nightly: bool) -> Result<()> {
|
||||
if cfg!(target_os = "linux") {
|
||||
std::env::set_var("CC", "clang");
|
||||
run!(
|
||||
@ -60,7 +56,9 @@ fn dist_server() -> Result<()> {
|
||||
// We'd want to add, but that requires setting the right linker somehow
|
||||
// --features=jemalloc
|
||||
)?;
|
||||
run!("strip ./target/x86_64-unknown-linux-musl/release/rust-analyzer")?;
|
||||
if !nightly {
|
||||
run!("strip ./target/x86_64-unknown-linux-musl/release/rust-analyzer")?;
|
||||
}
|
||||
} else {
|
||||
run!("cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --release")?;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
use crate::{
|
||||
codegen::Mode,
|
||||
not_bash::{fs2, pushd, rm_rf, run},
|
||||
not_bash::{date_iso, fs2, pushd, rm_rf, run},
|
||||
};
|
||||
|
||||
pub use anyhow::Result;
|
||||
@ -180,7 +180,7 @@ pub fn run_release(dry_run: bool) -> Result<()> {
|
||||
let website_root = project_root().join("../rust-analyzer.github.io");
|
||||
let changelog_dir = website_root.join("./thisweek/_posts");
|
||||
|
||||
let today = run!("date --iso")?;
|
||||
let today = date_iso()?;
|
||||
let commit = run!("git rev-parse HEAD")?;
|
||||
let changelog_n = fs2::read_dir(changelog_dir.as_path())?.count();
|
||||
|
||||
|
@ -13,7 +13,7 @@ use std::env;
|
||||
use pico_args::Arguments;
|
||||
use xtask::{
|
||||
codegen::{self, Mode},
|
||||
dist::{run_dist, ClientOpts},
|
||||
dist::run_dist,
|
||||
install::{ClientOpt, InstallCmd, ServerOpt},
|
||||
not_bash::pushd,
|
||||
pre_commit, project_root, run_clippy, run_fuzzer, run_pre_cache, run_release, run_rustfmt,
|
||||
@ -103,16 +103,10 @@ FLAGS:
|
||||
run_release(dry_run)
|
||||
}
|
||||
"dist" => {
|
||||
let client_opts = if args.contains("--client") {
|
||||
Some(ClientOpts {
|
||||
version: args.value_from_str("--version")?,
|
||||
release_tag: args.value_from_str("--tag")?,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let nightly = args.contains("--nightly");
|
||||
let client_version: Option<String> = args.opt_value_from_str("--client")?;
|
||||
args.finish()?;
|
||||
run_dist(client_opts)
|
||||
run_dist(nightly, client_version)
|
||||
}
|
||||
_ => {
|
||||
eprintln!(
|
||||
|
@ -94,6 +94,10 @@ pub fn run_process(cmd: String, echo: bool) -> Result<String> {
|
||||
run_process_inner(&cmd, echo).with_context(|| format!("process `{}` failed", cmd))
|
||||
}
|
||||
|
||||
pub fn date_iso() -> Result<String> {
|
||||
run!("date --iso --utc")
|
||||
}
|
||||
|
||||
fn run_process_inner(cmd: &str, echo: bool) -> Result<String> {
|
||||
let mut args = shelx(cmd);
|
||||
let binary = args.remove(0);
|
||||
|
Loading…
Reference in New Issue
Block a user