Merge from rustc

This commit is contained in:
Ralf Jung 2024-12-12 12:24:31 +01:00
commit f590fa9214
715 changed files with 16769 additions and 7776 deletions

View file

@ -5,7 +5,7 @@ use clippy_utils::{contains_name, get_parent_expr, in_automatically_derived, is_
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind, StructTailExpr};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_middle::ty::print::with_forced_trimmed_paths;
@ -285,7 +285,7 @@ fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Op
/// Returns whether `expr` is the update syntax base: `Foo { a: 1, .. base }`
fn is_update_syntax_base<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
if let Some(parent) = get_parent_expr(cx, expr)
&& let ExprKind::Struct(_, _, Some(base)) = parent.kind
&& let ExprKind::Struct(_, _, StructTailExpr::Base(base)) = parent.kind
{
base.hir_id == expr.hir_id
} else {

View file

@ -4,7 +4,7 @@ use clippy_utils::source::snippet_opt;
use rustc_ast::ast::{LitFloatType, LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{Visitor, walk_expr, walk_stmt};
use rustc_hir::{Block, Body, ConstContext, Expr, ExprKind, FnRetTy, HirId, Lit, Stmt, StmtKind};
use rustc_hir::{Block, Body, ConstContext, Expr, ExprKind, FnRetTy, HirId, Lit, Stmt, StmtKind, StructTailExpr};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, FloatTy, IntTy, PolyFnSig, Ty};
@ -197,7 +197,7 @@ impl<'tcx> Visitor<'tcx> for NumericFallbackVisitor<'_, 'tcx> {
}
// Visit base with no bound.
if let Some(base) = base {
if let StructTailExpr::Base(base) = base {
self.ty_bounds.push(ExplicitTyBound(false));
self.visit_expr(base);
self.ty_bounds.pop();

View file

@ -818,7 +818,6 @@ impl TyCoercionStability {
| TyKind::Typeof(..)
| TyKind::TraitObject(..)
| TyKind::InferDelegation(..)
| TyKind::AnonAdt(..)
| TyKind::Err(_) => Self::Reborrow,
};
}

View file

@ -3,7 +3,7 @@ use clippy_utils::fulfill_or_allowed;
use clippy_utils::source::snippet;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::{self as hir, ExprKind};
use rustc_hir::{self as hir, ExprKind, StructTailExpr};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::symbol::Symbol;
@ -95,7 +95,7 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
}
fields_snippet.push_str(&last_ident.to_string());
let base_snippet = if let Some(base) = base {
let base_snippet = if let StructTailExpr::Base(base) = base {
format!(", ..{}", snippet(cx, base.span, ".."))
} else {
String::new()

View file

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Expr, ExprKind};
use rustc_hir::{Expr, ExprKind, StructTailExpr};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::SyntaxContext;
@ -43,7 +43,7 @@ declare_lint_pass!(NumberedFields => [INIT_NUMBERED_FIELDS]);
impl<'tcx> LateLintPass<'tcx> for NumberedFields {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let ExprKind::Struct(path, fields @ [field, ..], None) = e.kind
if let ExprKind::Struct(path, fields @ [field, ..], StructTailExpr::None) = e.kind
// If the first character of any field is a digit it has to be a tuple.
&& field.ident.as_str().as_bytes().first().is_some_and(u8::is_ascii_digit)
// Type aliases can't be used as functions.

View file

@ -5,7 +5,7 @@ use clippy_utils::higher::ForLoop;
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::source::snippet;
use rustc_errors::Applicability;
use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind};
use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind, StructTailExpr};
use rustc_lint::LateContext;
use rustc_span::{Span, sym};
use std::iter::once;
@ -164,7 +164,7 @@ fn never_loop_expr<'tcx>(
},
ExprKind::Struct(_, fields, base) => {
let fields = never_loop_expr_all(cx, fields.iter().map(|f| f.expr), local_labels, main_loop_id);
if let Some(base) = base {
if let StructTailExpr::Base(base) = base {
combine_seq(fields, || never_loop_expr(cx, base, local_labels, main_loop_id))
} else {
fields

View file

@ -200,6 +200,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
cx.param_env,
ty,
traits::ObligationCause::dummy_with_span(span),
rustc_hir::Safety::Safe,
)
.is_ok()
{

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint;
use rustc_hir::{Expr, ExprKind};
use rustc_hir::{Expr, ExprKind, StructTailExpr};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::declare_lint_pass;
@ -51,7 +51,7 @@ declare_lint_pass!(NeedlessUpdate => [NEEDLESS_UPDATE]);
impl<'tcx> LateLintPass<'tcx> for NeedlessUpdate {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Struct(_, fields, Some(base)) = expr.kind {
if let ExprKind::Struct(_, fields, StructTailExpr::Base(base)) = expr.kind {
let ty = cx.typeck_results().expr_ty(expr);
if let ty::Adt(def, _) = ty.kind() {
if fields.len() == def.non_enum_variant().fields.len()

View file

@ -8,7 +8,7 @@ use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{
BinOpKind, BlockCheckMode, Expr, ExprKind, HirId, HirIdMap, ItemKind, LocalSource, Node, PatKind, Stmt, StmtKind,
UnsafeSource, is_range_literal,
UnsafeSource, StructTailExpr, is_range_literal,
};
use rustc_infer::infer::TyCtxtInferExt as _;
use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -238,7 +238,10 @@ fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
ExprKind::Struct(_, fields, ref base) => {
!has_drop(cx, cx.typeck_results().expr_ty(expr))
&& fields.iter().all(|field| has_no_effect(cx, field.expr))
&& base.as_ref().is_none_or(|base| has_no_effect(cx, base))
&& match &base {
StructTailExpr::None | StructTailExpr::DefaultFields(_) => true,
StructTailExpr::Base(base) => has_no_effect(cx, base),
}
},
ExprKind::Call(callee, args) => {
if let ExprKind::Path(ref qpath) = callee.kind {
@ -342,6 +345,10 @@ fn reduce_expression<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Vec
if has_drop(cx, cx.typeck_results().expr_ty(expr)) {
None
} else {
let base = match base {
StructTailExpr::Base(base) => Some(base),
StructTailExpr::None | StructTailExpr::DefaultFields(_) => None,
};
Some(fields.iter().map(|f| &f.expr).chain(base).map(Deref::deref).collect())
}
},

View file

@ -11,7 +11,7 @@ use rustc_hir::{
BodyId, Expr, ExprKind, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind, UnOp,
};
use rustc_lint::{LateContext, LateLintPass, Lint};
use rustc_middle::mir::interpret::{ErrorHandled, EvalToValTreeResult, GlobalId};
use rustc_middle::mir::interpret::{ErrorHandled, EvalToValTreeResult, GlobalId, ReportedErrorInfo};
use rustc_middle::ty::adjustment::Adjust;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_session::impl_lint_pass;
@ -302,7 +302,10 @@ impl<'tcx> NonCopyConst<'tcx> {
tcx.const_eval_global_id_for_typeck(typing_env, cid, span)
},
Ok(None) => Err(ErrorHandled::TooGeneric(span)),
Err(err) => Err(ErrorHandled::Reported(err.into(), span)),
Err(err) => Err(ErrorHandled::Reported(
ReportedErrorInfo::non_const_eval_error(err),
span,
)),
}
}
}

View file

@ -6,7 +6,7 @@ use clippy_utils::source::SpanRangeExt;
use clippy_utils::ty::implements_trait;
use rustc_ast::{LitIntType, LitKind, UintTy};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem, QPath};
use rustc_hir::{Expr, ExprKind, LangItem, QPath, StructTailExpr};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use std::fmt::{self, Display, Formatter};
@ -86,7 +86,7 @@ impl LateLintPass<'_> for SingleRangeInVecInit {
return;
};
let ExprKind::Struct(QPath::LangItem(lang_item, ..), [start, end], None) = inner_expr.kind else {
let ExprKind::Struct(QPath::LangItem(lang_item, ..), [start, end], StructTailExpr::None) = inner_expr.kind else {
return;
};

View file

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_copy;
use clippy_utils::{get_parent_expr, path_to_local};
use rustc_hir::{BindingMode, Expr, ExprField, ExprKind, Node, PatKind, Path, QPath, UnOp};
use rustc_hir::{BindingMode, Expr, ExprField, ExprKind, Node, PatKind, Path, QPath, UnOp, StructTailExpr};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
@ -59,15 +59,15 @@ impl LateLintPass<'_> for UnnecessaryStruct {
let field_path = same_path_in_all_fields(cx, expr, fields);
let sugg = match (field_path, base) {
(Some(&path), None) => {
(Some(&path), StructTailExpr::None | StructTailExpr::DefaultFields(_)) => {
// all fields match, no base given
path.span
},
(Some(path), Some(base)) if base_is_suitable(cx, expr, base) && path_matches_base(path, base) => {
(Some(path), StructTailExpr::Base(base)) if base_is_suitable(cx, expr, base) && path_matches_base(path, base) => {
// all fields match, has base: ensure that the path of the base matches
base.span
},
(None, Some(base)) if fields.is_empty() && base_is_suitable(cx, expr, base) => {
(None, StructTailExpr::Base(base)) if fields.is_empty() && base_is_suitable(cx, expr, base) => {
// just the base, no explicit fields
base.span
},

View file

@ -4,7 +4,7 @@ use rustc_ast::ast::{LitFloatType, LitKind};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::{
self as hir, BindingMode, CaptureBy, Closure, ClosureKind, ConstArg, ConstArgKind, CoroutineKind,
ExprKind, FnRetTy, HirId, Lit, PatKind, QPath, StmtKind, TyKind,
ExprKind, FnRetTy, HirId, Lit, PatKind, QPath, StmtKind, TyKind, StructTailExpr,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::declare_lint_pass;
@ -598,7 +598,10 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
},
ExprKind::Struct(qpath, fields, base) => {
bind!(self, qpath, fields);
opt_bind!(self, base);
let base = OptionPat::new(match base {
StructTailExpr::Base(base) => Some(self.bind("base", base)),
StructTailExpr::None | StructTailExpr::DefaultFields(_) => None,
});
kind!("Struct({qpath}, {fields}, {base})");
self.qpath(qpath);
self.slice(fields, |field| {

View file

@ -8,7 +8,7 @@ use crate::ty::is_type_diagnostic_item;
use rustc_ast::ast;
use rustc_hir as hir;
use rustc_hir::{Arm, Block, Expr, ExprKind, HirId, LoopSource, MatchSource, Node, Pat, QPath};
use rustc_hir::{Arm, Block, Expr, ExprKind, StructTailExpr, HirId, LoopSource, MatchSource, Node, Pat, QPath};
use rustc_lint::LateContext;
use rustc_span::{Span, sym, symbol};
@ -236,7 +236,7 @@ impl<'a> Range<'a> {
limits: ast::RangeLimits::Closed,
})
},
ExprKind::Struct(path, fields, None) => match (path, fields) {
ExprKind::Struct(path, fields, StructTailExpr::None) => match (path, fields) {
(QPath::LangItem(hir::LangItem::RangeFull, ..), []) => Some(Range {
start: None,
end: None,

View file

@ -10,7 +10,7 @@ use rustc_hir::{
AssocItemConstraint, BinOpKind, BindingMode, Block, BodyId, Closure, ConstArg, ConstArgKind, Expr,
ExprField, ExprKind, FnRetTy, GenericArg, GenericArgs, HirId, HirIdMap, InlineAsmOperand, LetExpr, Lifetime,
LifetimeName, Pat, PatField, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitBoundModifiers, Ty,
TyKind,
TyKind, StructTailExpr,
};
use rustc_lexer::{TokenKind, tokenize};
use rustc_lint::LateContext;
@ -380,7 +380,12 @@ impl HirEqInterExpr<'_, '_, '_> {
(ExprKind::Ret(l), ExprKind::Ret(r)) => both(l.as_ref(), r.as_ref(), |l, r| self.eq_expr(l, r)),
(&ExprKind::Struct(l_path, lf, ref lo), &ExprKind::Struct(r_path, rf, ref ro)) => {
self.eq_qpath(l_path, r_path)
&& both(lo.as_ref(), ro.as_ref(), |l, r| self.eq_expr(l, r))
&& match (lo, ro) {
(StructTailExpr::Base(l),StructTailExpr::Base(r)) => self.eq_expr(l, r),
(StructTailExpr::None, StructTailExpr::None) => true,
(StructTailExpr::DefaultFields(_), StructTailExpr::DefaultFields(_)) => true,
_ => false,
}
&& over(lf, rf, |l, r| self.eq_expr_field(l, r))
},
(&ExprKind::Tup(l_tup), &ExprKind::Tup(r_tup)) => self.eq_exprs(l_tup, r_tup),
@ -591,7 +596,6 @@ impl HirEqInterExpr<'_, '_, '_> {
(TyKind::Path(l), TyKind::Path(r)) => self.eq_qpath(l, r),
(&TyKind::Tup(l), &TyKind::Tup(r)) => over(l, r, |l, r| self.eq_ty(l, r)),
(&TyKind::Infer, &TyKind::Infer) => true,
(TyKind::AnonAdt(l_item_id), TyKind::AnonAdt(r_item_id)) => l_item_id == r_item_id,
_ => false,
}
}
@ -1017,7 +1021,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
self.hash_expr(f.expr);
}
if let Some(e) = *expr {
if let StructTailExpr::Base(e) = *expr {
self.hash_expr(e);
}
},
@ -1241,8 +1245,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
| TyKind::Infer
| TyKind::Never
| TyKind::InferDelegation(..)
| TyKind::OpaqueDef(_)
| TyKind::AnonAdt(_) => {},
| TyKind::OpaqueDef(_) => {},
}
}

View file

@ -7,7 +7,7 @@ use rustc_hir::def::{CtorKind, DefKind, Res};
use rustc_hir::intravisit::{self, Visitor, walk_block, walk_expr};
use rustc_hir::{
AnonConst, Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, LetExpr, Pat, QPath,
Safety, Stmt, UnOp, UnsafeSource,
Safety, Stmt, UnOp, UnsafeSource, StructTailExpr,
};
use rustc_lint::LateContext;
use rustc_middle::hir::nested_filter;
@ -663,7 +663,7 @@ pub fn for_each_unconsumed_temporary<'tcx, B>(
for field in fields {
helper(typeck, true, field.expr, f)?;
}
if let Some(default) = default {
if let StructTailExpr::Base(default) = default {
helper(typeck, false, default, f)?;
}
},

View file

@ -144,7 +144,7 @@ where
}
if !wrote_data {
println!("note: diff is identical to nightly rustdoc");
eprintln!("note: diff is identical to nightly rustdoc");
assert!(diff_output.metadata().unwrap().len() == 0);
return false;
} else if verbose {

View file

@ -20,7 +20,7 @@ pub(crate) fn configure_gdb(config: &Config) -> Option<Arc<Config>> {
}
if config.remote_test_client.is_some() && !config.target.contains("android") {
println!(
eprintln!(
"WARNING: debuginfo tests are not available when \
testing with remote"
);
@ -28,7 +28,7 @@ pub(crate) fn configure_gdb(config: &Config) -> Option<Arc<Config>> {
}
if config.target.contains("android") {
println!(
eprintln!(
"{} debug-info test uses tcp 5039 port.\
please reserve it",
config.target
@ -50,7 +50,7 @@ pub(crate) fn configure_lldb(config: &Config) -> Option<Arc<Config>> {
config.lldb_python_dir.as_ref()?;
if let Some(350) = config.lldb_version {
println!(
eprintln!(
"WARNING: The used version of LLDB (350) has a \
known issue that breaks debuginfo tests. See \
issue #32520 for more information. Skipping all \

View file

@ -188,8 +188,8 @@ pub fn parse_config(args: Vec<String>) -> Config {
let (argv0, args_) = args.split_first().unwrap();
if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
println!("{}", opts.usage(&message));
println!();
eprintln!("{}", opts.usage(&message));
eprintln!();
panic!()
}
@ -200,8 +200,8 @@ pub fn parse_config(args: Vec<String>) -> Config {
if matches.opt_present("h") || matches.opt_present("help") {
let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
println!("{}", opts.usage(&message));
println!();
eprintln!("{}", opts.usage(&message));
eprintln!();
panic!()
}
@ -508,7 +508,7 @@ pub fn run_tests(config: Arc<Config>) {
// easy to miss which tests failed, and as such fail to reproduce
// the failure locally.
println!(
eprintln!(
"Some tests failed in compiletest suite={}{} mode={} host={} target={}",
config.suite,
config

View file

@ -131,7 +131,7 @@ pub fn run(config: Arc<Config>, testpaths: &TestPaths, revision: Option<&str>) {
if config.verbose {
// We're going to be dumping a lot of info. Start on a new line.
print!("\n\n");
eprintln!("\n");
}
debug!("running {:?}", testpaths.file.display());
let mut props = TestProps::from_file(&testpaths.file, revision, &config);
@ -353,7 +353,7 @@ impl<'test> TestCx<'test> {
{
self.error(&format!("{} test did not emit an error", self.config.mode));
if self.config.mode == crate::common::Mode::Ui {
println!("note: by default, ui tests are expected not to compile");
eprintln!("note: by default, ui tests are expected not to compile");
}
proc_res.fatal(None, || ());
};
@ -774,20 +774,20 @@ impl<'test> TestCx<'test> {
unexpected.len(),
not_found.len()
));
println!("status: {}\ncommand: {}\n", proc_res.status, proc_res.cmdline);
eprintln!("status: {}\ncommand: {}\n", proc_res.status, proc_res.cmdline);
if !unexpected.is_empty() {
println!("{}", "--- unexpected errors (from JSON output) ---".green());
eprintln!("{}", "--- unexpected errors (from JSON output) ---".green());
for error in &unexpected {
println!("{}", error.render_for_expected());
eprintln!("{}", error.render_for_expected());
}
println!("{}", "---".green());
eprintln!("{}", "---".green());
}
if !not_found.is_empty() {
println!("{}", "--- not found errors (from test file) ---".red());
eprintln!("{}", "--- not found errors (from test file) ---".red());
for error in &not_found {
println!("{}", error.render_for_expected());
eprintln!("{}", error.render_for_expected());
}
println!("{}", "---\n".red());
eprintln!("{}", "---\n".red());
}
panic!("errors differ from expected");
}
@ -1876,18 +1876,18 @@ impl<'test> TestCx<'test> {
fn maybe_dump_to_stdout(&self, out: &str, err: &str) {
if self.config.verbose {
println!("------stdout------------------------------");
println!("{}", out);
println!("------stderr------------------------------");
println!("{}", err);
println!("------------------------------------------");
eprintln!("------stdout------------------------------");
eprintln!("{}", out);
eprintln!("------stderr------------------------------");
eprintln!("{}", err);
eprintln!("------------------------------------------");
}
}
fn error(&self, err: &str) {
match self.revision {
Some(rev) => println!("\nerror in revision `{}`: {}", rev, err),
None => println!("\nerror: {}", err),
Some(rev) => eprintln!("\nerror in revision `{}`: {}", rev, err),
None => eprintln!("\nerror: {}", err),
}
}
@ -1972,7 +1972,7 @@ impl<'test> TestCx<'test> {
if !self.config.has_html_tidy {
return;
}
println!("info: generating a diff against nightly rustdoc");
eprintln!("info: generating a diff against nightly rustdoc");
let suffix =
self.safe_revision().map_or("nightly".into(), |path| path.to_owned() + "-nightly");
@ -2082,7 +2082,7 @@ impl<'test> TestCx<'test> {
.output()
.unwrap();
assert!(output.status.success());
println!("{}", String::from_utf8_lossy(&output.stdout));
eprintln!("{}", String::from_utf8_lossy(&output.stdout));
eprintln!("{}", String::from_utf8_lossy(&output.stderr));
} else {
use colored::Colorize;
@ -2496,7 +2496,7 @@ impl<'test> TestCx<'test> {
)"#
)
.replace_all(&output, |caps: &Captures<'_>| {
println!("{}", &caps[0]);
eprintln!("{}", &caps[0]);
caps[0].replace(r"\", "/")
})
.replace("\r\n", "\n")
@ -2601,14 +2601,14 @@ impl<'test> TestCx<'test> {
if let Err(err) = fs::write(&actual_path, &actual) {
self.fatal(&format!("failed to write {stream} to `{actual_path:?}`: {err}",));
}
println!("Saved the actual {stream} to {actual_path:?}");
eprintln!("Saved the actual {stream} to {actual_path:?}");
let expected_path =
expected_output_path(self.testpaths, self.revision, &self.config.compare_mode, stream);
if !self.config.bless {
if expected.is_empty() {
println!("normalized {}:\n{}\n", stream, actual);
eprintln!("normalized {}:\n{}\n", stream, actual);
} else {
self.show_diff(
stream,
@ -2631,10 +2631,10 @@ impl<'test> TestCx<'test> {
if let Err(err) = fs::write(&expected_path, &actual) {
self.fatal(&format!("failed to write {stream} to `{expected_path:?}`: {err}"));
}
println!("Blessing the {stream} of {test_name} in {expected_path:?}");
eprintln!("Blessing the {stream} of {test_name} in {expected_path:?}");
}
println!("\nThe actual {0} differed from the expected {0}.", stream);
eprintln!("\nThe actual {0} differed from the expected {0}.", stream);
if self.config.bless { 0 } else { 1 }
}
@ -2783,7 +2783,7 @@ impl<'test> TestCx<'test> {
fs::create_dir_all(&incremental_dir).unwrap();
if self.config.verbose {
println!("init_incremental_test: incremental_dir={}", incremental_dir.display());
eprintln!("init_incremental_test: incremental_dir={}", incremental_dir.display());
}
}
@ -2841,7 +2841,7 @@ impl ProcRes {
}
}
println!(
eprintln!(
"status: {}\ncommand: {}\n{}\n{}\n",
self.status,
self.cmdline,
@ -2852,7 +2852,7 @@ impl ProcRes {
pub fn fatal(&self, err: Option<&str>, on_failure: impl FnOnce()) -> ! {
if let Some(e) = err {
println!("\nerror: {}", e);
eprintln!("\nerror: {}", e);
}
self.print_info();
on_failure();

View file

@ -64,13 +64,13 @@ impl TestCx<'_> {
if !missing.is_empty() {
missing.sort();
println!("\nThese items should have been contained but were not:\n");
eprintln!("\nThese items should have been contained but were not:\n");
for item in &missing {
println!("{}", item);
eprintln!("{}", item);
}
println!("\n");
eprintln!("\n");
}
if !unexpected.is_empty() {
@ -80,24 +80,24 @@ impl TestCx<'_> {
sorted
};
println!("\nThese items were contained but should not have been:\n");
eprintln!("\nThese items were contained but should not have been:\n");
for item in sorted {
println!("{}", item);
eprintln!("{}", item);
}
println!("\n");
eprintln!("\n");
}
if !wrong_cgus.is_empty() {
wrong_cgus.sort_by_key(|pair| pair.0.name.clone());
println!("\nThe following items were assigned to wrong codegen units:\n");
eprintln!("\nThe following items were assigned to wrong codegen units:\n");
for &(ref expected_item, ref actual_item) in &wrong_cgus {
println!("{}", expected_item.name);
println!(" expected: {}", codegen_units_to_str(&expected_item.codegen_units));
println!(" actual: {}", codegen_units_to_str(&actual_item.codegen_units));
println!();
eprintln!("{}", expected_item.name);
eprintln!(" expected: {}", codegen_units_to_str(&expected_item.codegen_units));
eprintln!(" actual: {}", codegen_units_to_str(&actual_item.codegen_units));
eprintln!();
}
}

View file

@ -260,7 +260,7 @@ impl TestCx<'_> {
cmdline,
};
if adb.kill().is_err() {
println!("Adb process is already finished.");
eprintln!("Adb process is already finished.");
}
} else {
let rust_src_root =
@ -275,7 +275,7 @@ impl TestCx<'_> {
match self.config.gdb_version {
Some(version) => {
println!("NOTE: compiletest thinks it is using GDB version {}", version);
eprintln!("NOTE: compiletest thinks it is using GDB version {}", version);
if version > extract_gdb_version("7.4").unwrap() {
// Add the directory containing the pretty printers to
@ -297,7 +297,7 @@ impl TestCx<'_> {
}
}
_ => {
println!(
eprintln!(
"NOTE: compiletest does not know which version of \
GDB it is using"
);
@ -392,10 +392,10 @@ impl TestCx<'_> {
match self.config.lldb_version {
Some(ref version) => {
println!("NOTE: compiletest thinks it is using LLDB version {}", version);
eprintln!("NOTE: compiletest thinks it is using LLDB version {}", version);
}
_ => {
println!(
eprintln!(
"NOTE: compiletest does not know which version of \
LLDB it is using"
);

View file

@ -30,7 +30,7 @@ impl TestCx<'_> {
assert!(incremental_dir.exists(), "init_incremental_test failed to create incremental dir");
if self.config.verbose {
print!("revision={:?} props={:#?}", revision, self.props);
eprint!("revision={:?} props={:#?}", revision, self.props);
}
if revision.starts_with("cpass") {

View file

@ -89,7 +89,7 @@ impl TestCx<'_> {
}
let expected_string = fs::read_to_string(&expected_file).unwrap();
if dumped_string != expected_string {
print!("{}", write_diff(&expected_string, &dumped_string, 3));
eprint!("{}", write_diff(&expected_string, &dumped_string, 3));
panic!(
"Actual MIR output differs from expected MIR output {}",
expected_file.display()

View file

@ -29,7 +29,7 @@ impl TestCx<'_> {
if !res.status.success() {
self.fatal_proc_rec_with_ctx("jsondocck failed!", &res, |_| {
println!("Rustdoc Output:");
eprintln!("Rustdoc Output:");
proc_res.print_info();
})
}

View file

@ -109,10 +109,10 @@ impl TestCx<'_> {
}
if errors > 0 {
println!("To update references, rerun the tests and pass the `--bless` flag");
eprintln!("To update references, rerun the tests and pass the `--bless` flag");
let relative_path_to_file =
self.testpaths.relative_dir.join(self.testpaths.file.file_name().unwrap());
println!(
eprintln!(
"To only update this specific test, also pass `--test-args {}`",
relative_path_to_file.display(),
);

View file

@ -30,7 +30,7 @@ fn path_div() -> &'static str {
pub fn logv(config: &Config, s: String) {
debug!("{}", s);
if config.verbose {
println!("{}", s);
eprintln!("{}", s);
}
}

View file

@ -28,7 +28,8 @@ impl Cache {
}
}
pub fn value(&self) -> &Value {
&self.value
// FIXME: Make this failible, so jsonpath syntax error has line number.
pub fn select(&self, path: &str) -> Vec<&Value> {
jsonpath_lib::select(&self.value, path).unwrap()
}
}

View file

@ -1,29 +1,7 @@
use std::error::Error;
use std::fmt;
use crate::Command;
#[derive(Debug)]
pub enum CkError {
/// A check failed. File didn't exist or failed to match the command
FailedCheck(String, Command),
/// An error triggered by some other error
Induced(Box<dyn Error>),
}
impl fmt::Display for CkError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CkError::FailedCheck(msg, cmd) => {
write!(f, "Failed check: {} on line {}", msg, cmd.lineno)
}
CkError::Induced(err) => write!(f, "Check failed: {}", err),
}
}
}
impl<T: Error + 'static> From<T> for CkError {
fn from(err: T) -> CkError {
CkError::Induced(Box::new(err))
}
pub struct CkError {
pub message: String,
pub command: Command,
}

View file

@ -1,8 +1,8 @@
use std::borrow::Cow;
use std::process::ExitCode;
use std::sync::OnceLock;
use std::{env, fmt, fs};
use std::{env, fs};
use jsonpath_lib::select;
use regex::{Regex, RegexBuilder};
use serde_json::Value;
@ -14,90 +14,134 @@ use cache::Cache;
use config::parse_config;
use error::CkError;
fn main() -> Result<(), String> {
fn main() -> ExitCode {
let config = parse_config(env::args().collect());
let mut failed = Vec::new();
let mut cache = Cache::new(&config);
let commands = get_commands(&config.template)
.map_err(|_| format!("Jsondocck failed for {}", &config.template))?;
let Ok(commands) = get_commands(&config.template) else {
eprintln!("Jsondocck failed for {}", &config.template);
return ExitCode::FAILURE;
};
for command in commands {
if let Err(e) = check_command(command, &mut cache) {
failed.push(e);
if let Err(message) = check_command(&command, &mut cache) {
failed.push(CkError { command, message });
}
}
if failed.is_empty() {
Ok(())
ExitCode::SUCCESS
} else {
for i in failed {
eprintln!("{}", i);
eprintln!("{}:{}, command failed", config.template, i.command.lineno);
eprintln!("{}", i.message)
}
Err(format!("Jsondocck failed for {}", &config.template))
ExitCode::FAILURE
}
}
#[derive(Debug)]
pub struct Command {
negated: bool,
kind: CommandKind,
args: Vec<String>,
path: String,
lineno: usize,
}
#[derive(Debug)]
pub enum CommandKind {
Has,
Count,
Is,
IsMany,
Set,
enum CommandKind {
/// `//@ has <path>`
///
/// Checks the path exists.
HasPath,
/// `//@ has <path> <value>`
///
/// Check one thing at the path is equal to the value.
HasValue { value: String },
/// `//@ !has <path>`
///
/// Checks the path doesn't exist.
HasNotPath,
/// `//@ is <path> <value>`
///
/// Check the path is the given value.
Is { value: String },
/// `//@ is <path> <value> <value>...`
///
/// Check that the path matches to exactly every given value.
IsMany { values: Vec<String> },
/// `//@ !is <path> <value>`
///
/// Check the path isn't the given value.
IsNot { value: String },
/// `//@ count <path> <value>`
///
/// Check the path has the expected number of matches.
CountIs { expected: usize },
/// `//@ set <name> = <path>`
Set { variable: String },
}
impl CommandKind {
fn validate(&self, args: &[String], lineno: usize) -> bool {
// FIXME(adotinthevoid): We should "parse, don't validate" here, so we avoid ad-hoc
// indexing in check_command.
let count = match self {
CommandKind::Has => (1..=2).contains(&args.len()),
CommandKind::IsMany => args.len() >= 2,
CommandKind::Count | CommandKind::Is => 2 == args.len(),
CommandKind::Set => 3 == args.len(),
};
if !count {
print_err(&format!("Incorrect number of arguments to `{}`", self), lineno);
return false;
}
if let CommandKind::Count = self {
if args[1].parse::<usize>().is_err() {
print_err(
&format!(
"Second argument to `count` must be a valid usize (got `{}`)",
args[1]
),
lineno,
);
return false;
/// Returns both the kind and the path.
///
/// Returns `None` if the command isn't from jsondocck (e.g. from compiletest).
fn parse<'a>(command_name: &str, negated: bool, args: &'a [String]) -> Option<(Self, &'a str)> {
let kind = match (command_name, negated) {
("count", false) => {
assert_eq!(args.len(), 2);
let expected = args[1].parse().expect("invalid number for `count`");
Self::CountIs { expected }
}
}
true
}
}
("ismany", false) => {
// FIXME: Make this >= 3, and migrate len(values)==1 cases to @is
assert!(args.len() >= 2, "Not enough args to `ismany`");
let values = args[1..].to_owned();
Self::IsMany { values }
}
impl fmt::Display for CommandKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let text = match self {
CommandKind::Has => "has",
CommandKind::IsMany => "ismany",
CommandKind::Count => "count",
CommandKind::Is => "is",
CommandKind::Set => "set",
("is", false) => {
assert_eq!(args.len(), 2);
Self::Is { value: args[1].clone() }
}
("is", true) => {
assert_eq!(args.len(), 2);
Self::IsNot { value: args[1].clone() }
}
("set", false) => {
assert_eq!(args.len(), 3);
assert_eq!(args[1], "=");
return Some((Self::Set { variable: args[0].clone() }, &args[2]));
}
("has", false) => match args {
[_path] => Self::HasPath,
[_path, value] => Self::HasValue { value: value.clone() },
_ => panic!("`//@ has` must have 2 or 3 arguments, but got {args:?}"),
},
("has", true) => {
assert_eq!(args.len(), 1, "args={args:?}");
Self::HasNotPath
}
(_, false) if KNOWN_DIRECTIVE_NAMES.contains(&command_name) => {
return None;
}
_ => {
panic!("Invalid command `//@ {}{command_name}`", if negated { "!" } else { "" })
}
};
write!(f, "{}", text)
Some((kind, &args[0]))
}
}
@ -125,8 +169,7 @@ fn print_err(msg: &str, lineno: usize) {
// See <https://github.com/rust-lang/rust/issues/125813#issuecomment-2141953780>.
include!(concat!(env!("CARGO_MANIFEST_DIR"), "/../compiletest/src/directive-list.rs"));
/// Get a list of commands from a file. Does the work of ensuring the commands
/// are syntactically valid.
/// Get a list of commands from a file.
fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
let mut commands = Vec::new();
let mut errors = false;
@ -142,217 +185,102 @@ fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
let negated = cap.name("negated").unwrap().as_str() == "!";
let cmd = match cap.name("cmd").unwrap().as_str() {
"has" => CommandKind::Has,
"count" => CommandKind::Count,
"is" => CommandKind::Is,
"ismany" => CommandKind::IsMany,
"set" => CommandKind::Set,
// FIXME: See the comment above the `include!(...)`.
cmd if KNOWN_DIRECTIVE_NAMES.contains(&cmd) => continue,
cmd => {
print_err(&format!("Unrecognized command name `{cmd}`"), lineno);
errors = true;
continue;
}
};
let args = cap.name("args").map_or(Some(vec![]), |m| shlex::split(m.as_str()));
let args = match args {
let args_str = &cap["args"];
let args = match shlex::split(args_str) {
Some(args) => args,
None => {
print_err(
&format!(
"Invalid arguments to shlex::split: `{}`",
cap.name("args").unwrap().as_str()
),
lineno,
);
print_err(&format!("Invalid arguments to shlex::split: `{args_str}`",), lineno);
errors = true;
continue;
}
};
if !cmd.validate(&args, lineno) {
errors = true;
continue;
if let Some((kind, path)) = CommandKind::parse(&cap["cmd"], negated, &args) {
commands.push(Command { kind, lineno, path: path.to_owned() })
}
commands.push(Command { negated, kind: cmd, args, lineno })
}
if !errors { Ok(commands) } else { Err(()) }
}
/// Performs the actual work of ensuring a command passes. Generally assumes the command
/// is syntactically valid.
fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
// FIXME: Be more granular about why, (e.g. syntax error, count not equal)
let result = match command.kind {
CommandKind::Has => {
match command.args.len() {
// `has <jsonpath>`: Check that `jsonpath` exists.
1 => {
let val = cache.value();
let results = select(val, &command.args[0]).unwrap();
!results.is_empty()
}
// `has <jsonpath> <value>`: Check *any* item matched by `jsonpath` equals `value`.
2 => {
let val = cache.value().clone();
let results = select(&val, &command.args[0]).unwrap();
let pat = string_to_value(&command.args[1], cache);
let has = results.contains(&pat.as_ref());
// Give better error for when `has` check fails.
if !command.negated && !has {
return Err(CkError::FailedCheck(
format!(
"{} matched to {:?} but didn't have {:?}",
&command.args[0],
results,
pat.as_ref()
),
command,
));
} else {
has
}
}
_ => unreachable!(),
/// Performs the actual work of ensuring a command passes.
fn check_command(command: &Command, cache: &mut Cache) -> Result<(), String> {
let matches = cache.select(&command.path);
match &command.kind {
CommandKind::HasPath => {
if matches.is_empty() {
return Err("matched to no values".to_owned());
}
}
CommandKind::HasNotPath => {
if !matches.is_empty() {
return Err(format!("matched to {matches:?}, but wanted no matches"));
}
}
CommandKind::HasValue { value } => {
let want_value = string_to_value(value, cache);
if !matches.contains(&want_value.as_ref()) {
return Err(format!("matched to {matches:?}, which didn't contain {want_value:?}"));
}
}
CommandKind::Is { value } => {
let want_value = string_to_value(value, cache);
let matched = get_one(&matches)?;
if matched != want_value.as_ref() {
return Err(format!("matched to {matched:?} but want {want_value:?}"));
}
}
CommandKind::IsNot { value } => {
let wantnt_value = string_to_value(value, cache);
let matched = get_one(&matches)?;
if matched == wantnt_value.as_ref() {
return Err(format!("got value {wantnt_value:?}, but want anything else"));
}
}
// `ismany <path> <jsonpath> <value...>`
CommandKind::IsMany => {
assert!(!command.negated, "`ismany` may not be negated");
let (query, values) = if let [query, values @ ..] = &command.args[..] {
(query, values)
} else {
unreachable!("Checked in CommandKind::validate")
};
let val = cache.value();
let got_values = select(val, &query).unwrap();
CommandKind::IsMany { values } => {
// Serde json doesn't implement Ord or Hash for Value, so we must
// use a Vec here. While in theory that makes setwize equality
// O(n^2), in practice n will never be large enough to matter.
let expected_values =
values.iter().map(|v| string_to_value(v, cache)).collect::<Vec<_>>();
if expected_values.len() != got_values.len() {
return Err(CkError::FailedCheck(
format!(
"Expected {} values, but `{}` matched to {} values ({:?})",
expected_values.len(),
query,
got_values.len(),
got_values
),
command,
if expected_values.len() != matches.len() {
return Err(format!(
"Expected {} values, but matched to {} values ({:?})",
expected_values.len(),
matches.len(),
matches
));
};
for got_value in got_values {
for got_value in matches {
if !expected_values.iter().any(|exp| &**exp == got_value) {
return Err(CkError::FailedCheck(
format!("`{}` has match {:?}, which was not expected", query, got_value),
command,
));
return Err(format!("has match {got_value:?}, which was not expected",));
}
}
true
}
// `count <jsonpath> <count>`: Check that `jsonpath` matches exactly `count` times.
CommandKind::Count => {
assert_eq!(command.args.len(), 2);
let expected: usize = command.args[1].parse().unwrap();
let val = cache.value();
let results = select(val, &command.args[0]).unwrap();
let eq = results.len() == expected;
if !command.negated && !eq {
return Err(CkError::FailedCheck(
format!(
"`{}` matched to `{:?}` with length {}, but expected length {}",
&command.args[0],
results,
results.len(),
expected
),
command,
CommandKind::CountIs { expected } => {
if *expected != matches.len() {
return Err(format!(
"matched to `{matches:?}` with length {}, but expected length {expected}",
matches.len(),
));
} else {
eq
}
}
// `has <jsonpath> <value>`: Check` *exactly one* item matched by `jsonpath`, and it equals `value`.
CommandKind::Is => {
assert_eq!(command.args.len(), 2);
let val = cache.value().clone();
let results = select(&val, &command.args[0]).unwrap();
let pat = string_to_value(&command.args[1], cache);
let is = results.len() == 1 && results[0] == pat.as_ref();
if !command.negated && !is {
return Err(CkError::FailedCheck(
format!(
"{} matched to {:?}, but expected {:?}",
&command.args[0],
results,
pat.as_ref()
),
command,
));
} else {
is
}
CommandKind::Set { variable } => {
let value = get_one(&matches)?;
let r = cache.variables.insert(variable.to_owned(), value.clone());
assert!(r.is_none(), "name collision: {variable:?} is duplicated");
}
// `set <name> = <jsonpath>`
CommandKind::Set => {
assert!(!command.negated, "`set` may not be negated");
assert_eq!(command.args.len(), 3);
assert_eq!(command.args[1], "=", "Expected an `=`");
let val = cache.value().clone();
let results = select(&val, &command.args[2]).unwrap();
assert_eq!(
results.len(),
1,
"Expected 1 match for `{}` (because of `set`): matched to {:?}",
command.args[2],
results
);
match results.len() {
0 => false,
1 => {
let r = cache.variables.insert(command.args[0].clone(), results[0].clone());
assert!(r.is_none(), "Name collision: {} is duplicated", command.args[0]);
true
}
_ => {
panic!(
"Got multiple results in `set` for `{}`: {:?}",
&command.args[2], results,
);
}
}
}
};
}
if result == command.negated {
if command.negated {
Err(CkError::FailedCheck(
format!("`!{} {}` matched when it shouldn't", command.kind, command.args.join(" ")),
command,
))
} else {
// FIXME: In the future, try 'peeling back' each step, and see at what level the match failed
Err(CkError::FailedCheck(
format!(
"`{} {}` didn't match when it should",
command.kind,
command.args.join(" ")
),
command,
))
}
} else {
Ok(())
Ok(())
}
fn get_one<'a>(matches: &[&'a Value]) -> Result<&'a Value, String> {
match matches {
[] => Err("matched to no values".to_owned()),
[matched] => Ok(matched),
_ => Err(format!("matched to multiple values {matches:?}, but want exactly 1")),
}
}

View file

@ -1,5 +1,4 @@
use rustc_abi::{ExternAbi, Size};
use rustc_ast::ast::Mutability;
use rustc_middle::ty::layout::LayoutOf as _;
use rustc_middle::ty::{self, Instance, Ty};
use rustc_span::{BytePos, Loc, Symbol, hygiene};
@ -179,14 +178,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
match flags {
0 => {
// These are "mutable" allocations as we consider them to be owned by the callee.
let name_alloc =
this.allocate_str(&name, MiriMemoryKind::Rust.into(), Mutability::Mut)?;
let filename_alloc =
this.allocate_str(&filename, MiriMemoryKind::Rust.into(), Mutability::Mut)?;
this.write_immediate(name_alloc.to_ref(this), &this.project_field(dest, 0)?)?;
this.write_immediate(filename_alloc.to_ref(this), &this.project_field(dest, 1)?)?;
throw_unsup_format!("miri_resolve_frame: v0 is not supported any more");
}
1 => {
this.write_scalar(

View file

@ -12,7 +12,6 @@
//! metadata we remembered when pushing said frame.
use rustc_abi::ExternAbi;
use rustc_ast::Mutability;
use rustc_middle::{mir, ty};
use rustc_target::spec::PanicStrategy;
@ -161,7 +160,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let this = self.eval_context_mut();
// First arg: message.
let msg = this.allocate_str(msg, MiriMemoryKind::Machine.into(), Mutability::Not)?;
let msg = this.allocate_str_dedup(msg)?;
// Call the lang item.
let panic = this.tcx.lang_items().panic_fn().unwrap();
@ -180,7 +179,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let this = self.eval_context_mut();
// First arg: message.
let msg = this.allocate_str(msg, MiriMemoryKind::Machine.into(), Mutability::Not)?;
let msg = this.allocate_str_dedup(msg)?;
// Call the lang item.
let panic = this.tcx.lang_items().panic_nounwind().unwrap();

View file

@ -1,69 +0,0 @@
//@normalize-stderr-test: "::<.*>" -> ""
#[inline(never)]
fn func_a() -> Box<[*mut ()]> {
func_b::<u8>()
}
#[inline(never)]
fn func_b<T>() -> Box<[*mut ()]> {
func_c()
}
macro_rules! invoke_func_d {
() => {
func_d()
};
}
#[inline(never)]
fn func_c() -> Box<[*mut ()]> {
invoke_func_d!()
}
#[inline(never)]
fn func_d() -> Box<[*mut ()]> {
unsafe { miri_get_backtrace(0) }
}
fn main() {
let mut seen_main = false;
let frames = func_a();
for frame in frames.iter() {
let miri_frame = unsafe { miri_resolve_frame(*frame, 0) };
let name = String::from_utf8(miri_frame.name.into()).unwrap();
let filename = String::from_utf8(miri_frame.filename.into()).unwrap();
if name == "func_a" {
assert_eq!(func_a as *mut (), miri_frame.fn_ptr);
}
// Print every frame to stderr.
let out = format!("{}:{}:{} ({})", filename, miri_frame.lineno, miri_frame.colno, name);
eprintln!("{}", out);
// Print the 'main' frame (and everything before it) to stdout, skipping
// the printing of internal (and possibly fragile) libstd frames.
// Stdout is less normalized so we see more, but it also means we can print less
// as platform differences would lead to test suite failures.
if !seen_main {
println!("{}", out);
seen_main = name == "main";
}
}
}
// This goes at the bottom of the file so that we can change it
// without disturbing line numbers of the functions in the backtrace.
extern "Rust" {
fn miri_get_backtrace(flags: u64) -> Box<[*mut ()]>;
fn miri_resolve_frame(ptr: *mut (), flags: u64) -> MiriFrame;
}
#[derive(Debug)]
#[repr(C)]
struct MiriFrame {
name: Box<[u8]>,
filename: Box<[u8]>,
lineno: u32,
colno: u32,
fn_ptr: *mut (),
}

View file

@ -1,18 +0,0 @@
tests/pass/backtrace/backtrace-api-v0.rs:LL:CC (func_d)
tests/pass/backtrace/backtrace-api-v0.rs:LL:CC (func_c)
tests/pass/backtrace/backtrace-api-v0.rs:LL:CC (func_b)
tests/pass/backtrace/backtrace-api-v0.rs:LL:CC (func_a)
tests/pass/backtrace/backtrace-api-v0.rs:LL:CC (main)
RUSTLIB/core/src/ops/function.rs:LL:CC (<fn() as std::ops::FnOnce<()>>::call_once - shim(fn()))
RUSTLIB/std/src/sys/backtrace.rs:LL:CC (std::sys::backtrace::__rust_begin_short_backtrace)
RUSTLIB/std/src/rt.rs:LL:CC (std::rt::lang_start::{closure#0})
RUSTLIB/core/src/ops/function.rs:LL:CC (std::ops::function::impls::call_once)
RUSTLIB/std/src/panicking.rs:LL:CC (std::panicking::r#try::do_call)
RUSTLIB/std/src/panicking.rs:LL:CC (std::panicking::r#try)
RUSTLIB/std/src/panic.rs:LL:CC (std::panic::catch_unwind)
RUSTLIB/std/src/rt.rs:LL:CC (std::rt::lang_start_internal::{closure#1})
RUSTLIB/std/src/panicking.rs:LL:CC (std::panicking::r#try::do_call)
RUSTLIB/std/src/panicking.rs:LL:CC (std::panicking::r#try)
RUSTLIB/std/src/panic.rs:LL:CC (std::panic::catch_unwind)
RUSTLIB/std/src/rt.rs:LL:CC (std::rt::lang_start_internal)
RUSTLIB/std/src/rt.rs:LL:CC (std::rt::lang_start)

View file

@ -1,5 +0,0 @@
tests/pass/backtrace/backtrace-api-v0.rs:24:14 (func_d)
tests/pass/backtrace/backtrace-api-v0.rs:14:9 (func_c)
tests/pass/backtrace/backtrace-api-v0.rs:9:5 (func_b::<u8>)
tests/pass/backtrace/backtrace-api-v0.rs:5:5 (func_a)
tests/pass/backtrace/backtrace-api-v0.rs:29:18 (main)

View file

@ -1,7 +1,7 @@
# If you want to use this as an .envrc file to create a shell with necessery components
# to develop rustc, use the following command in the root of the rusr checkout:
#
# ln -s ./src/tools/nix-dev-shell/envrc-flake ./.envrc && nix flake update --flake ./src/tools/nix-dev-shell && echo .envrc >> .git/info/exclude
# ln -s ./src/tools/nix-dev-shell/envrc-flake ./.envrc && nix flake update --flake ./src/tools/nix-dev-shell
if nix flake show path:./src/tools/nix-dev-shell &> /dev/null; then
use flake path:./src/tools/nix-dev-shell

View file

@ -1,7 +1,7 @@
# If you want to use this as an .envrc file to create a shell with necessery components
# to develop rustc, use the following command in the root of the rusr checkout:
#
# ln -s ./src/tools/nix-dev-shell/envrc-shell ./.envrc && echo .envrc >> .git/info/exclude
# ln -s ./src/tools/nix-dev-shell/envrc-shell ./.envrc
use nix ./src/tools/nix-dev-shell/shell.nix

View file

@ -24,9 +24,8 @@
# Avoid creating text files for ICEs.
RUSTC_ICE = "0";
# Provide `libstdc++.so.6` for the self-contained lld.
LD_LIBRARY_PATH = "${with pkgs; lib.makeLibraryPath [
stdenv.cc.cc.lib
]}";
# Provide `libz.so.1`.
LD_LIBRARY_PATH = "${with pkgs; lib.makeLibraryPath [stdenv.cc.cc.lib zlib]}";
};
}
);

View file

@ -13,7 +13,6 @@ pkgs.mkShell {
# Avoid creating text files for ICEs.
RUSTC_ICE = "0";
# Provide `libstdc++.so.6` for the self-contained lld.
LD_LIBRARY_PATH = "${with pkgs; lib.makeLibraryPath [
stdenv.cc.cc.lib
]}";
# Provide `libz.so.1`
LD_LIBRARY_PATH = "${with pkgs; lib.makeLibraryPath [stdenv.cc.cc.lib zlib]}";
}

View file

@ -7,7 +7,7 @@ edition = "2021"
bstr = "1.6.0"
object = "0.36.2"
similar = "2.5.0"
wasmparser = { version = "0.216", default-features = false, features = ["std"] }
wasmparser = { version = "0.219", default-features = false, features = ["std"] }
regex = "1.8" # 1.8 to avoid memchr 2.6.0, as 2.5.0 is pinned in the workspace
gimli = "0.31.0"
build_helper = { path = "../../build_helper" }

View file

@ -329,7 +329,7 @@ impl CompletedProcess {
/// Checks that `stderr` does not contain the regex pattern `unexpected`.
#[track_caller]
pub fn assert_stderr_not_contains_regex<S: AsRef<str>>(&self, unexpected: S) -> &Self {
assert_not_contains_regex(&self.stdout_utf8(), unexpected);
assert_not_contains_regex(&self.stderr_utf8(), unexpected);
self
}

View file

@ -325,6 +325,12 @@ impl Rustc {
self
}
/// Pass the `--verbose` flag.
pub fn verbose(&mut self) -> &mut Self {
self.cmd.arg("--verbose");
self
}
/// `EXTRARSCXXFLAGS`
pub fn extra_rs_cxx_flags(&mut self) -> &mut Self {
// Adapted from tools.mk (trimmed):

View file

@ -84,6 +84,12 @@ dependencies = [
"vfs",
]
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -509,6 +515,7 @@ dependencies = [
"base-db",
"cfg",
"either",
"expect-test",
"hir-def",
"hir-expand",
"hir-ty",
@ -519,6 +526,9 @@ dependencies = [
"span",
"stdx",
"syntax",
"syntax-bridge",
"test-fixture",
"test-utils",
"tracing",
"triomphe",
"tt",
@ -1492,9 +1502,9 @@ dependencies = [
[[package]]
name = "ra-ap-rustc_abi"
version = "0.80.0"
version = "0.85.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613760a3071b25a67a8d7bc97b37c7fd4722562e9479137b83ae9cf8f8c1601a"
checksum = "af462c3a2d524b84a51b6848b439787f01b35c6c1086d3e3086a5f5eea92ed9a"
dependencies = [
"bitflags 2.6.0",
"ra-ap-rustc_index",
@ -1503,20 +1513,19 @@ dependencies = [
[[package]]
name = "ra-ap-rustc_index"
version = "0.80.0"
version = "0.85.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b2bc6b4ecede8ff28295041e22c2e66853f8e0125990c05135bad3c30bad12c"
checksum = "be6bb8cb0ab78d94a222f1ffd3e87254cdfb57413382b8d6ebe26a85482f99d1"
dependencies = [
"arrayvec",
"ra-ap-rustc_index_macros",
"smallvec",
]
[[package]]
name = "ra-ap-rustc_index_macros"
version = "0.80.0"
version = "0.85.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2374a39fb2d92d0509178c2b442eadca3cc10e403ef9729a040c1855b08ff261"
checksum = "c24b1641455b46e87435b7321219672077066e678963d239a4a2904732979b16"
dependencies = [
"proc-macro2",
"quote",
@ -1525,9 +1534,9 @@ dependencies = [
[[package]]
name = "ra-ap-rustc_lexer"
version = "0.80.0"
version = "0.85.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a2cf8e48b69af3ecc29ed3449892e8a999111d2f75212a78aa242e117cf1711"
checksum = "94daa86974417981fed2f12bd8fb00158dfa6fee561152bed689278c846d0272"
dependencies = [
"unicode-properties",
"unicode-xid",
@ -1535,9 +1544,9 @@ dependencies = [
[[package]]
name = "ra-ap-rustc_parse_format"
version = "0.80.0"
version = "0.85.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d6f59a22b559263c5c42747ae362cf5d4fb272293fa119a4623f8ec288f9656"
checksum = "fc07f6bd581746f358e39c4b6bfe8d455b3d6ad1a857821016d0d42eeb5e1e3e"
dependencies = [
"ra-ap-rustc_index",
"ra-ap-rustc_lexer",
@ -1545,9 +1554,9 @@ dependencies = [
[[package]]
name = "ra-ap-rustc_pattern_analysis"
version = "0.80.0"
version = "0.85.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7d0575b54ffe09bc5d2f158454bc05f0c30c01d9992310965f854be50ae22b8"
checksum = "2f49b86e1276c1c3c72898410def29b699415f4e7d1dfb3531daf79794694372"
dependencies = [
"ra-ap-rustc_index",
"rustc-hash 2.0.0",
@ -1645,6 +1654,7 @@ version = "0.0.0"
dependencies = [
"always-assert",
"anyhow",
"base64",
"cargo_metadata",
"cfg",
"crossbeam-channel",
@ -1655,6 +1665,7 @@ dependencies = [
"hir-def",
"hir-ty",
"ide",
"ide-completion",
"ide-db",
"ide-ssr",
"intern",
@ -1683,6 +1694,7 @@ dependencies = [
"stdx",
"syntax",
"syntax-bridge",
"tenthash",
"test-fixture",
"test-utils",
"tikv-jemallocator",
@ -1986,6 +1998,12 @@ dependencies = [
"tt",
]
[[package]]
name = "tenthash"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d67f9f3cf70e0852941d7bc3cb884b49b24b8ee956baf91ad0abae31f5ef11fb"
[[package]]
name = "test-fixture"
version = "0.0.0"

View file

@ -84,11 +84,11 @@ tt = { path = "./crates/tt", version = "0.0.0" }
vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" }
vfs = { path = "./crates/vfs", version = "0.0.0" }
ra-ap-rustc_lexer = { version = "0.80", default-features = false }
ra-ap-rustc_parse_format = { version = "0.80", default-features = false }
ra-ap-rustc_index = { version = "0.80", default-features = false }
ra-ap-rustc_abi = { version = "0.80", default-features = false }
ra-ap-rustc_pattern_analysis = { version = "0.80", default-features = false }
ra-ap-rustc_lexer = { version = "0.85", default-features = false }
ra-ap-rustc_parse_format = { version = "0.85", default-features = false }
ra-ap-rustc_index = { version = "0.85", default-features = false }
ra-ap-rustc_abi = { version = "0.85", default-features = false }
ra-ap-rustc_pattern_analysis = { version = "0.85", default-features = false }
# local crates that aren't published to crates.io. These should not have versions.
test-fixture = { path = "./crates/test-fixture" }

View file

@ -547,29 +547,6 @@ impl CrateGraph {
None
}
// Work around for https://github.com/rust-lang/rust-analyzer/issues/6038.
// As hacky as it gets.
pub fn patch_cfg_if(&mut self) -> bool {
// we stupidly max by version in an attempt to have all duplicated std's depend on the same cfg_if so that deduplication still works
let cfg_if =
self.hacky_find_crate("cfg_if").max_by_key(|&it| self.arena[it].version.clone());
let std = self.hacky_find_crate("std").next();
match (cfg_if, std) {
(Some(cfg_if), Some(std)) => {
self.arena[cfg_if].dependencies.clear();
self.arena[std]
.dependencies
.push(Dependency::new(CrateName::new("cfg_if").unwrap(), cfg_if));
true
}
_ => false,
}
}
fn hacky_find_crate<'a>(&'a self, display_name: &'a str) -> impl Iterator<Item = CrateId> + 'a {
self.iter().filter(move |it| self[*it].display_name.as_deref() == Some(display_name))
}
/// Removes all crates from this crate graph except for the ones in `to_keep` and fixes up the dependencies.
/// Returns a mapping from old crate ids to new crate ids.
pub fn remove_crates_except(&mut self, to_keep: &[CrateId]) -> Vec<Option<CrateId>> {

View file

@ -31,7 +31,7 @@ use crate::{
path::{ModPath, Path},
src::HasSource,
type_ref::{TypeRef, TypeRefId, TypesMap, TypesSourceMap},
BlockId, DefWithBodyId, HasModule, Lookup,
BlockId, DefWithBodyId, HasModule, Lookup, SyntheticSyntax,
};
/// A wrapper around [`span::SyntaxContextId`] that is intended only for comparisons.
@ -141,7 +141,7 @@ pub struct BodySourceMap {
field_map_back: FxHashMap<ExprId, FieldSource>,
pat_field_map_back: FxHashMap<PatId, PatFieldSource>,
types: TypesSourceMap,
pub types: TypesSourceMap,
// FIXME: Make this a sane struct.
template_map: Option<
@ -160,9 +160,6 @@ pub struct BodySourceMap {
diagnostics: Vec<BodyDiagnostic>,
}
#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
pub struct SyntheticSyntax;
#[derive(Debug, Eq, PartialEq)]
pub enum BodyDiagnostic {
InactiveCode { node: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions },
@ -408,7 +405,8 @@ impl Body {
f(else_branch);
}
}
Expr::Let { expr, .. } => {
Expr::Let { expr, pat } => {
self.walk_exprs_in_pat(*pat, &mut f);
f(*expr);
}
Expr::Block { statements, tail, .. }
@ -442,6 +440,137 @@ impl Body {
f(*receiver);
args.iter().copied().for_each(f);
}
Expr::Match { expr, arms } => {
f(*expr);
arms.iter().for_each(|arm| {
f(arm.expr);
self.walk_exprs_in_pat(arm.pat, &mut f);
});
}
Expr::Break { expr, .. }
| Expr::Return { expr }
| Expr::Yield { expr }
| Expr::Yeet { expr } => {
if let &Some(expr) = expr {
f(expr);
}
}
Expr::Become { expr } => f(*expr),
Expr::RecordLit { fields, spread, .. } => {
for field in fields.iter() {
f(field.expr);
}
if let &Some(expr) = spread {
f(expr);
}
}
Expr::Closure { body, .. } => {
f(*body);
}
Expr::BinaryOp { lhs, rhs, .. } => {
f(*lhs);
f(*rhs);
}
Expr::Range { lhs, rhs, .. } => {
if let &Some(lhs) = rhs {
f(lhs);
}
if let &Some(rhs) = lhs {
f(rhs);
}
}
Expr::Index { base, index, .. } => {
f(*base);
f(*index);
}
Expr::Field { expr, .. }
| Expr::Await { expr }
| Expr::Cast { expr, .. }
| Expr::Ref { expr, .. }
| Expr::UnaryOp { expr, .. }
| Expr::Box { expr } => {
f(*expr);
}
Expr::Tuple { exprs, .. } => exprs.iter().copied().for_each(f),
Expr::Array(a) => match a {
Array::ElementList { elements, .. } => elements.iter().copied().for_each(f),
Array::Repeat { initializer, repeat } => {
f(*initializer);
f(*repeat)
}
},
&Expr::Assignment { target, value } => {
self.walk_exprs_in_pat(target, &mut f);
f(value);
}
}
}
pub fn walk_child_exprs_without_pats(&self, expr_id: ExprId, mut f: impl FnMut(ExprId)) {
let expr = &self[expr_id];
match expr {
Expr::Continue { .. }
| Expr::Const(_)
| Expr::Missing
| Expr::Path(_)
| Expr::OffsetOf(_)
| Expr::Literal(_)
| Expr::Underscore => {}
Expr::InlineAsm(it) => it.operands.iter().for_each(|(_, op)| match op {
AsmOperand::In { expr, .. }
| AsmOperand::Out { expr: Some(expr), .. }
| AsmOperand::InOut { expr, .. } => f(*expr),
AsmOperand::SplitInOut { in_expr, out_expr, .. } => {
f(*in_expr);
if let Some(out_expr) = out_expr {
f(*out_expr);
}
}
AsmOperand::Out { expr: None, .. }
| AsmOperand::Const(_)
| AsmOperand::Label(_)
| AsmOperand::Sym(_) => (),
}),
Expr::If { condition, then_branch, else_branch } => {
f(*condition);
f(*then_branch);
if let &Some(else_branch) = else_branch {
f(else_branch);
}
}
Expr::Let { expr, .. } => {
f(*expr);
}
Expr::Block { statements, tail, .. }
| Expr::Unsafe { statements, tail, .. }
| Expr::Async { statements, tail, .. } => {
for stmt in statements.iter() {
match stmt {
Statement::Let { initializer, else_branch, .. } => {
if let &Some(expr) = initializer {
f(expr);
}
if let &Some(expr) = else_branch {
f(expr);
}
}
Statement::Expr { expr: expression, .. } => f(*expression),
Statement::Item(_) => (),
}
}
if let &Some(expr) = tail {
f(expr);
}
}
Expr::Loop { body, .. } => f(*body),
Expr::Call { callee, args, .. } => {
f(*callee);
args.iter().copied().for_each(f);
}
Expr::MethodCall { receiver, args, .. } => {
f(*receiver);
args.iter().copied().for_each(f);
}
Expr::Match { expr, arms } => {
f(*expr);
arms.iter().map(|arm| arm.expr).for_each(f);
@ -498,10 +627,7 @@ impl Body {
f(*repeat)
}
},
&Expr::Assignment { target, value } => {
self.walk_exprs_in_pat(target, &mut f);
f(value);
}
&Expr::Assignment { target: _, value } => f(value),
}
}

View file

@ -1510,20 +1510,20 @@ impl ExprCollector<'_> {
BuiltinShadowMode::Other,
None,
);
// Funnily enough, record structs/variants *can* be shadowed
// by pattern bindings (but unit or tuple structs/variants
// can't).
match resolved.take_values() {
Some(ModuleDefId::ConstId(_)) => (None, Pat::Path(name.into())),
Some(ModuleDefId::EnumVariantId(_)) => {
// this is only really valid for unit variants, but
// shadowing other enum variants with a pattern is
// an error anyway
Some(ModuleDefId::EnumVariantId(variant))
if self.db.variant_data(variant.into()).kind()
!= StructKind::Record =>
{
(None, Pat::Path(name.into()))
}
Some(ModuleDefId::AdtId(AdtId::StructId(s)))
if self.db.struct_data(s).variant_data.kind() != StructKind::Record =>
{
// Funnily enough, record structs *can* be shadowed
// by pattern bindings (but unit or tuple structs
// can't).
(None, Pat::Path(name.into()))
}
// shadowing statics is an error as well, so we just ignore that case here

View file

@ -1,6 +1,5 @@
mod block;
use base_db::SourceDatabase;
use expect_test::{expect, Expect};
use test_fixture::WithFixture;
@ -11,7 +10,7 @@ use super::*;
fn lower(ra_fixture: &str) -> (TestDB, Arc<Body>, DefWithBodyId) {
let db = TestDB::with_files(ra_fixture);
let krate = db.crate_graph().iter().next().unwrap();
let krate = db.fetch_test_crate();
let def_map = db.crate_def_map(krate);
let mut fn_def = None;
'outer: for (_, module) in def_map.modules() {
@ -404,3 +403,26 @@ fn foo() {
}"#]]
.assert_eq(&body.pretty_print(&db, def, Edition::CURRENT))
}
#[test]
fn shadowing_record_variant() {
let (_, body, _) = lower(
r#"
enum A {
B { field: i32 },
}
fn f() {
use A::*;
match () {
B => {}
};
}
"#,
);
assert_eq!(body.bindings.len(), 1, "should have a binding for `B`");
assert_eq!(
body.bindings[BindingId::from_raw(RawIdx::from_u32(0))].name.as_str(),
"B",
"should have a binding for `B`",
);
}

View file

@ -369,7 +369,7 @@ impl ImplData {
let item_tree = tree_id.item_tree(db);
let impl_def = &item_tree[tree_id.value];
let target_trait = impl_def.target_trait.clone();
let target_trait = impl_def.target_trait;
let self_ty = impl_def.self_ty;
let is_negative = impl_def.is_negative;
let is_unsafe = impl_def.is_unsafe;

View file

@ -26,8 +26,8 @@ use crate::{
nameres::{DefMap, MacroSubNs},
path::{AssociatedTypeBinding, GenericArg, GenericArgs, NormalPath, Path},
type_ref::{
ArrayType, ConstRef, FnType, LifetimeRef, RefType, TypeBound, TypeRef, TypeRefId, TypesMap,
TypesSourceMap,
ArrayType, ConstRef, FnType, LifetimeRef, PathId, RefType, TypeBound, TypeRef, TypeRefId,
TypesMap, TypesSourceMap,
},
AdtId, ConstParamId, GenericDefId, HasModule, ItemTreeLoc, LifetimeParamId,
LocalLifetimeParamId, LocalTypeOrConstParamId, Lookup, TypeOrConstParamId, TypeParamId,
@ -224,6 +224,11 @@ impl GenericParams {
self.len() == 0
}
#[inline]
pub fn no_predicates(&self) -> bool {
self.where_predicates.is_empty()
}
#[inline]
pub fn where_predicates(&self) -> std::slice::Iter<'_, WherePredicate> {
self.where_predicates.iter()
@ -874,14 +879,20 @@ fn copy_type_bound(
to: &mut TypesMap,
to_source_map: &mut TypesSourceMap,
) -> TypeBound {
match bound {
TypeBound::Path(path, modifier) => {
TypeBound::Path(copy_path(path, from, from_source_map, to, to_source_map), *modifier)
let mut copy_path_id = |path: PathId| {
let new_path = copy_path(&from[path], from, from_source_map, to, to_source_map);
let new_path_id = to.types.alloc(TypeRef::Path(new_path));
if let Some(&ptr) = from_source_map.types_map_back.get(path.type_ref()) {
to_source_map.types_map_back.insert(new_path_id, ptr);
}
PathId::from_type_ref_unchecked(new_path_id)
};
match bound {
&TypeBound::Path(path, modifier) => TypeBound::Path(copy_path_id(path), modifier),
TypeBound::ForLifetime(lifetimes, path) => {
TypeBound::ForLifetime(lifetimes.clone(), copy_path_id(*path))
}
TypeBound::ForLifetime(lifetimes, path) => TypeBound::ForLifetime(
lifetimes.clone(),
copy_path(path, from, from_source_map, to, to_source_map),
),
TypeBound::Lifetime(lifetime) => TypeBound::Lifetime(lifetime.clone()),
TypeBound::Use(use_args) => TypeBound::Use(use_args.clone()),
TypeBound::Error => TypeBound::Error,

View file

@ -23,6 +23,7 @@ use crate::{
hir::Literal,
lower::LowerCtx,
path::{GenericArg, Path},
SyntheticSyntax,
};
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
@ -91,19 +92,37 @@ impl Rawness {
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
/// A `TypeRefId` that is guaranteed to always be `TypeRef::Path`. We use this for things like
/// impl's trait, that are always paths but need to be traced back to source code.
pub struct PathId(TypeRefId);
impl PathId {
#[inline]
pub fn from_type_ref_unchecked(type_ref: TypeRefId) -> Self {
Self(type_ref)
}
#[inline]
pub fn type_ref(self) -> TypeRefId {
self.0
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct TraitRef {
pub path: Path,
pub path: PathId,
}
impl TraitRef {
/// Converts an `ast::PathType` to a `hir::TraitRef`.
pub(crate) fn from_ast(ctx: &mut LowerCtx<'_>, node: ast::Type) -> Option<Self> {
// FIXME: Use `Path::from_src`
match node {
ast::Type::PathType(path) => {
path.path().and_then(|it| ctx.lower_path(it)).map(|path| TraitRef { path })
}
match &node {
ast::Type::PathType(path) => path
.path()
.and_then(|it| ctx.lower_path(it))
.map(|path| TraitRef { path: ctx.alloc_path(path, AstPtr::new(&node)) }),
_ => None,
}
}
@ -173,11 +192,24 @@ impl TypesMap {
impl Index<TypeRefId> for TypesMap {
type Output = TypeRef;
#[inline]
fn index(&self, index: TypeRefId) -> &Self::Output {
&self.types[index]
}
}
impl Index<PathId> for TypesMap {
type Output = Path;
#[inline]
fn index(&self, index: PathId) -> &Self::Output {
let TypeRef::Path(path) = &self[index.type_ref()] else {
unreachable!("`PathId` always points to `TypeRef::Path`");
};
path
}
}
pub type TypePtr = AstPtr<ast::Type>;
pub type TypeSource = InFile<TypePtr>;
@ -187,6 +219,12 @@ pub struct TypesSourceMap {
}
impl TypesSourceMap {
pub const EMPTY: Self = Self { types_map_back: ArenaMap::new() };
pub fn type_syntax(&self, id: TypeRefId) -> Result<TypeSource, SyntheticSyntax> {
self.types_map_back.get(id).cloned().ok_or(SyntheticSyntax)
}
pub(crate) fn shrink_to_fit(&mut self) {
let TypesSourceMap { types_map_back } = self;
types_map_back.shrink_to_fit();
@ -214,15 +252,15 @@ impl LifetimeRef {
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum TypeBound {
Path(Path, TraitBoundModifier),
ForLifetime(Box<[Name]>, Path),
Path(PathId, TraitBoundModifier),
ForLifetime(Box<[Name]>, PathId),
Lifetime(LifetimeRef),
Use(Box<[UseArgRef]>),
Error,
}
#[cfg(target_pointer_width = "64")]
const _: [(); 32] = [(); ::std::mem::size_of::<TypeBound>()];
const _: [(); 24] = [(); ::std::mem::size_of::<TypeBound>()];
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum UseArgRef {
@ -365,8 +403,8 @@ impl TypeRef {
TypeRef::ImplTrait(bounds) | TypeRef::DynTrait(bounds) => {
for bound in bounds {
match bound {
TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => {
go_path(path, f, map)
&TypeBound::Path(path, _) | &TypeBound::ForLifetime(_, path) => {
go_path(&map[path], f, map)
}
TypeBound::Lifetime(_) | TypeBound::Error | TypeBound::Use(_) => (),
}
@ -397,8 +435,8 @@ impl TypeRef {
}
for bound in binding.bounds.iter() {
match bound {
TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => {
go_path(path, f, map)
&TypeBound::Path(path, _) | &TypeBound::ForLifetime(_, path) => {
go_path(&map[path], f, map)
}
TypeBound::Lifetime(_) | TypeBound::Error | TypeBound::Use(_) => (),
}
@ -425,7 +463,7 @@ pub(crate) fn type_bounds_from_ast(
impl TypeBound {
pub(crate) fn from_ast(ctx: &mut LowerCtx<'_>, node: ast::TypeBound) -> Self {
let mut lower_path_type = |path_type: ast::PathType| ctx.lower_path(path_type.path()?);
let mut lower_path_type = |path_type: &ast::PathType| ctx.lower_path(path_type.path()?);
match node.kind() {
ast::TypeBoundKind::PathType(path_type) => {
@ -433,8 +471,10 @@ impl TypeBound {
Some(_) => TraitBoundModifier::Maybe,
None => TraitBoundModifier::None,
};
lower_path_type(path_type)
.map(|p| TypeBound::Path(p, m))
lower_path_type(&path_type)
.map(|p| {
TypeBound::Path(ctx.alloc_path(p, AstPtr::new(&path_type).upcast()), m)
})
.unwrap_or(TypeBound::Error)
}
ast::TypeBoundKind::ForType(for_type) => {
@ -445,12 +485,14 @@ impl TypeBound {
.collect(),
None => Box::default(),
};
let path = for_type.ty().and_then(|ty| match ty {
ast::Type::PathType(path_type) => lower_path_type(path_type),
let path = for_type.ty().and_then(|ty| match &ty {
ast::Type::PathType(path_type) => lower_path_type(path_type).map(|p| (p, ty)),
_ => None,
});
match path {
Some(p) => TypeBound::ForLifetime(lt_refs, p),
Some((p, ty)) => {
TypeBound::ForLifetime(lt_refs, ctx.alloc_path(p, AstPtr::new(&ty)))
}
None => TypeBound::Error,
}
}
@ -470,10 +512,10 @@ impl TypeBound {
}
}
pub fn as_path(&self) -> Option<(&Path, &TraitBoundModifier)> {
pub fn as_path<'a>(&self, map: &'a TypesMap) -> Option<(&'a Path, TraitBoundModifier)> {
match self {
TypeBound::Path(p, m) => Some((p, m)),
TypeBound::ForLifetime(_, p) => Some((p, &TraitBoundModifier::None)),
&TypeBound::Path(p, m) => Some((&map[p], m)),
&TypeBound::ForLifetime(_, p) => Some((&map[p], TraitBoundModifier::None)),
TypeBound::Lifetime(_) | TypeBound::Error | TypeBound::Use(_) => None,
}
}

View file

@ -34,7 +34,7 @@ use crate::{
lower::LowerCtx,
path::AssociatedTypeBinding,
type_ref::{
LifetimeRef, RefType, TraitBoundModifier, TraitRef, TypeBound, TypeRef, TypeRefId,
LifetimeRef, PathId, RefType, TraitBoundModifier, TraitRef, TypeBound, TypeRef, TypeRefId,
TypesMap, TypesSourceMap,
},
visibility::RawVisibility,
@ -514,7 +514,7 @@ impl<'a> Ctx<'a> {
};
let ret_type = if func.async_token().is_some() {
let future_impl = desugar_future_path(ret_type);
let future_impl = desugar_future_path(&mut body_ctx, ret_type);
let ty_bound = TypeBound::Path(future_impl, TraitBoundModifier::None);
body_ctx.alloc_type_ref_desugared(TypeRef::ImplTrait(ThinVec::from_iter([ty_bound])))
} else {
@ -936,7 +936,7 @@ impl<'a> Ctx<'a> {
}
}
fn desugar_future_path(orig: TypeRefId) -> Path {
fn desugar_future_path(ctx: &mut LowerCtx<'_>, orig: TypeRefId) -> PathId {
let path = path![core::future::Future];
let mut generic_args: Vec<_> =
std::iter::repeat(None).take(path.segments().len() - 1).collect();
@ -948,7 +948,8 @@ fn desugar_future_path(orig: TypeRefId) -> Path {
};
generic_args.push(Some(GenericArgs { bindings: Box::new([binding]), ..GenericArgs::empty() }));
Path::from_known_path(path, generic_args)
let path = Path::from_known_path(path, generic_args);
PathId::from_type_ref_unchecked(ctx.alloc_type_ref_desugared(TypeRef::Path(path)))
}
enum HasImplicitSelf {

View file

@ -484,7 +484,7 @@ impl Printer<'_> {
w!(self, "!");
}
if let Some(tr) = target_trait {
self.print_path(&tr.path, types_map);
self.print_path(&types_map[tr.path], types_map);
w!(self, " for ");
}
self.print_type_ref(*self_ty, types_map);
@ -648,9 +648,9 @@ impl Printer<'_> {
let (target, bound) = match pred {
WherePredicate::TypeBound { target, bound } => (target, bound),
WherePredicate::Lifetime { target, bound } => {
wln!(
w!(
this,
"{}: {},",
"{}: {}",
target.name.display(self.db.upcast(), edition),
bound.name.display(self.db.upcast(), edition)
);

View file

@ -351,7 +351,8 @@ trait Tr<'a, T: 'a>: Super where Self: for<'a> Tr<'a, T> {}
where
T: Copy,
T: 'a,
T: 'b
T: 'b,
'b: 'a
{
pub(self) field: &'a &'b T,
}
@ -370,7 +371,8 @@ trait Tr<'a, T: 'a>: Super where Self: for<'a> Tr<'a, T> {}
where
T: Copy,
T: 'a,
T: 'b
T: 'b,
'b: 'a
{
// AstId: 9
pub(self) fn f<G>(

View file

@ -376,6 +376,9 @@ language_item_table! {
Fn, sym::fn_, fn_trait, Target::Trait, GenericRequirement::Exact(1);
FnMut, sym::fn_mut, fn_mut_trait, Target::Trait, GenericRequirement::Exact(1);
FnOnce, sym::fn_once, fn_once_trait, Target::Trait, GenericRequirement::Exact(1);
AsyncFn, sym::async_fn, async_fn_trait, Target::Trait, GenericRequirement::Exact(1);
AsyncFnMut, sym::async_fn_mut, async_fn_mut_trait, Target::Trait, GenericRequirement::Exact(1);
AsyncFnOnce, sym::async_fn_once, async_fn_once_trait, Target::Trait, GenericRequirement::Exact(1);
FnOnceOutput, sym::fn_once_output, fn_once_output, Target::AssocTy, GenericRequirement::None;

View file

@ -1535,3 +1535,6 @@ fn macro_call_as_call_id_with_eager(
pub struct UnresolvedMacro {
pub path: hir_expand::mod_path::ModPath,
}
#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
pub struct SyntheticSyntax;

View file

@ -2,7 +2,7 @@
use std::{cell::OnceCell, mem};
use hir_expand::{span_map::SpanMap, AstId, HirFileId, InFile};
use span::{AstIdMap, AstIdNode};
use span::{AstIdMap, AstIdNode, Edition, EditionedFileId, FileId, RealSpanMap};
use stdx::thin_vec::ThinVec;
use syntax::ast;
use triomphe::Arc;
@ -10,7 +10,7 @@ use triomphe::Arc;
use crate::{
db::DefDatabase,
path::Path,
type_ref::{TypeBound, TypePtr, TypeRef, TypeRefId, TypesMap, TypesSourceMap},
type_ref::{PathId, TypeBound, TypePtr, TypeRef, TypeRefId, TypesMap, TypesSourceMap},
};
pub struct LowerCtx<'a> {
@ -63,6 +63,30 @@ impl<'a> LowerCtx<'a> {
}
}
/// Prepares a `LowerCtx` for synthetic AST that needs to be lowered. This is intended for IDE things.
pub fn for_synthetic_ast(
db: &'a dyn DefDatabase,
ast_id_map: Arc<AstIdMap>,
types_map: &'a mut TypesMap,
types_source_map: &'a mut TypesSourceMap,
) -> Self {
let file_id = EditionedFileId::new(
FileId::from_raw(EditionedFileId::MAX_FILE_ID),
Edition::Edition2015,
);
LowerCtx {
db,
// Make up an invalid file id, so that if we will try to actually access it salsa will panic.
file_id: file_id.into(),
span_map: SpanMap::RealSpanMap(Arc::new(RealSpanMap::absolute(file_id))).into(),
ast_id_map: ast_id_map.into(),
impl_trait_bounds: Vec::new(),
outer_impl_trait: false,
types_map,
types_source_map,
}
}
pub(crate) fn span_map(&self) -> &SpanMap {
self.span_map.get_or_init(|| self.db.span_map(self.file_id))
}
@ -118,4 +142,8 @@ impl<'a> LowerCtx<'a> {
pub(crate) fn alloc_error_type(&mut self) -> TypeRefId {
self.types_map.types.alloc(TypeRef::Error)
}
pub(crate) fn alloc_path(&mut self, path: Path, node: TypePtr) -> PathId {
PathId::from_type_ref_unchecked(self.alloc_type_ref(TypeRef::Path(path), node))
}
}

View file

@ -1733,7 +1733,7 @@ m!(C("0"));
macro_rules! m {
($k:expr) => { fn f() { K::$k; } }
}
/* parse error: expected identifier */
/* parse error: expected identifier, `self`, `super`, `crate`, or `Self` */
/* parse error: expected SEMICOLON */
/* parse error: expected SEMICOLON */
/* parse error: expected expression, item or let statement */
@ -1759,8 +1759,9 @@ fn f() {
// NAME_REF@6..7
// IDENT@6..7 "K"
// COLON2@7..9 "::"
// ERROR@9..10
// L_PAREN@9..10 "("
// PATH_SEGMENT@9..10
// ERROR@9..10
// L_PAREN@9..10 "("
// EXPR_STMT@10..16
// CALL_EXPR@10..16
// PATH_EXPR@10..11

View file

@ -184,3 +184,31 @@ fn test() {
"#]],
);
}
#[test]
fn meta_variable_raw_name_equals_non_raw() {
check(
r#"
macro_rules! m {
($r#name:tt) => {
$name
}
}
fn test() {
m!(1234)
}
"#,
expect![[r#"
macro_rules! m {
($r#name:tt) => {
$name
}
}
fn test() {
1234
}
"#]],
);
}

View file

@ -16,7 +16,6 @@ mod proc_macros;
use std::{iter, ops::Range, sync};
use base_db::SourceDatabase;
use expect_test::Expect;
use hir_expand::{
db::ExpandDatabase,
@ -63,7 +62,7 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream
},
)];
let db = TestDB::with_files_extra_proc_macros(ra_fixture, extra_proc_macros);
let krate = db.crate_graph().iter().next().unwrap();
let krate = db.fetch_test_crate();
let def_map = db.crate_def_map(krate);
let local_id = DefMap::ROOT;
let module = def_map.module_id(local_id);

View file

@ -910,8 +910,13 @@ impl DefCollector<'_> {
self.update(module_id, &items, vis, Some(ImportType::Glob(id)));
// record the glob import in case we add further items
let glob = self.glob_imports.entry(m.local_id).or_default();
if !glob.iter().any(|(mid, _, _)| *mid == module_id) {
glob.push((module_id, vis, id));
match glob.iter_mut().find(|(mid, _, _)| *mid == module_id) {
None => glob.push((module_id, vis, id)),
Some((_, old_vis, _)) => {
if let Some(new_vis) = old_vis.max(vis, &self.def_map) {
*old_vis = new_vis;
}
}
}
}
}

View file

@ -13,13 +13,13 @@ use crate::{db::DefDatabase, nameres::DefMap, test_db::TestDB};
fn compute_crate_def_map(ra_fixture: &str) -> Arc<DefMap> {
let db = TestDB::with_files(ra_fixture);
let krate = db.crate_graph().iter().next().unwrap();
let krate = db.fetch_test_crate();
db.crate_def_map(krate)
}
fn render_crate_def_map(ra_fixture: &str) -> String {
let db = TestDB::with_files(ra_fixture);
let krate = db.crate_graph().iter().next().unwrap();
let krate = db.fetch_test_crate();
db.crate_def_map(krate).dump(&db)
}

View file

@ -451,3 +451,42 @@ mod glob_target {
"#]],
);
}
#[test]
fn regression_18580() {
check(
r#"
pub mod libs {
pub struct Placeholder;
}
pub mod reexport_2 {
use reexport_1::*;
pub use reexport_1::*;
pub mod reexport_1 {
pub use crate::libs::*;
}
}
use reexport_2::*;
"#,
expect![[r#"
crate
Placeholder: t v
libs: t
reexport_1: t
reexport_2: t
crate::libs
Placeholder: t v
crate::reexport_2
Placeholder: t v
reexport_1: t
crate::reexport_2::reexport_1
Placeholder: t v
"#]],
);
}

View file

@ -1,15 +1,11 @@
use base_db::{SourceDatabase, SourceDatabaseFileInputExt as _};
use base_db::SourceDatabaseFileInputExt as _;
use test_fixture::WithFixture;
use crate::{db::DefDatabase, nameres::tests::TestDB, AdtId, ModuleDefId};
fn check_def_map_is_not_recomputed(ra_fixture_initial: &str, ra_fixture_change: &str) {
let (mut db, pos) = TestDB::with_position(ra_fixture_initial);
let krate = {
let crate_graph = db.crate_graph();
// Some of these tests use minicore/proc-macros which will be injected as the first crate
crate_graph.iter().last().unwrap()
};
let krate = db.fetch_test_crate();
{
let events = db.log_executed(|| {
db.crate_def_map(krate);
@ -120,28 +116,31 @@ fn f() { foo }
);
}
#[test]
fn typing_inside_an_attribute_arg_should_not_invalidate_def_map() {
check_def_map_is_not_recomputed(
r"
//- proc_macros: identity
//- /lib.rs
mod foo;
// Would be nice if this was the case, but as attribute inputs are stored in the item tree, this is
// not currently the case.
// #[test]
// fn typing_inside_an_attribute_arg_should_not_invalidate_def_map() {
// check_def_map_is_not_recomputed(
// r"
// //- proc_macros: identity
// //- /lib.rs
// mod foo;
//- /foo/mod.rs
pub mod bar;
// //- /foo/mod.rs
// pub mod bar;
// //- /foo/bar.rs
// $0
// #[proc_macros::identity]
// fn f() {}
// ",
// r"
// #[proc_macros::identity(foo)]
// fn f() {}
// ",
// );
// }
//- /foo/bar.rs
$0
#[proc_macros::identity]
fn f() {}
",
r"
#[proc_macros::identity(foo)]
fn f() {}
",
);
}
#[test]
fn typing_inside_macro_heavy_file_should_not_invalidate_def_map() {
check_def_map_is_not_recomputed(
@ -198,31 +197,33 @@ pub struct S {}
);
}
#[test]
fn typing_inside_a_derive_should_not_invalidate_def_map() {
check_def_map_is_not_recomputed(
r"
//- proc_macros: derive_identity
//- minicore:derive
//- /lib.rs
mod foo;
// Would be nice if this was the case, but as attribute inputs are stored in the item tree, this is
// not currently the case.
// #[test]
// fn typing_inside_a_derive_should_not_invalidate_def_map() {
// check_def_map_is_not_recomputed(
// r"
// //- proc_macros: derive_identity
// //- minicore:derive
// //- /lib.rs
// mod foo;
//- /foo/mod.rs
pub mod bar;
// //- /foo/mod.rs
// pub mod bar;
//- /foo/bar.rs
$0
#[derive(proc_macros::DeriveIdentity)]
#[allow()]
struct S;
",
r"
#[derive(proc_macros::DeriveIdentity)]
#[allow(dead_code)]
struct S;
",
);
}
// //- /foo/bar.rs
// $0
// #[derive(proc_macros::DeriveIdentity)]
// #[allow()]
// struct S;
// ",
// r"
// #[derive(proc_macros::DeriveIdentity)]
// #[allow(dead_code)]
// struct S;
// ",
// );
// }
#[test]
fn typing_inside_a_function_should_not_invalidate_item_expansions() {

View file

@ -1,5 +1,7 @@
//! A desugared representation of paths like `crate::foo` or `<Type as Trait>::bar`.
mod lower;
#[cfg(test)]
mod tests;
use std::{
fmt::{self, Display},
@ -19,6 +21,8 @@ use syntax::ast;
pub use hir_expand::mod_path::{path, ModPath, PathKind};
pub use lower::hir_segment_to_ast_segment;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ImportAlias {
/// Unnamed alias, as in `use Foo as _;`
@ -230,7 +234,7 @@ impl Path {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PathSegment<'a> {
pub name: &'a Name,
pub args_and_bindings: Option<&'a GenericArgs>,
@ -274,6 +278,12 @@ impl<'a> PathSegments<'a> {
generic_args: self.generic_args.map(|it| it.get(..len).unwrap_or(it)),
}
}
pub fn strip_last(&self) -> PathSegments<'a> {
PathSegments {
segments: self.segments.split_last().map_or(&[], |it| it.1),
generic_args: self.generic_args.map(|it| it.split_last().map_or(&[][..], |it| it.1)),
}
}
pub fn iter(&self) -> impl Iterator<Item = PathSegment<'a>> {
self.segments
.iter()

View file

@ -17,13 +17,31 @@ use crate::{
type_ref::{LifetimeRef, TypeBound, TypeRef},
};
#[cfg(test)]
thread_local! {
/// This is used to test `hir_segment_to_ast_segment()`. It's a hack, but it makes testing much easier.
pub(super) static SEGMENT_LOWERING_MAP: std::cell::RefCell<rustc_hash::FxHashMap<ast::PathSegment, usize>> = std::cell::RefCell::default();
}
/// Converts an `ast::Path` to `Path`. Works with use trees.
/// It correctly handles `$crate` based path from macro call.
// If you modify the logic of the lowering, make sure to check if `hir_segment_to_ast_segment()`
// also needs an update.
pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<Path> {
let mut kind = PathKind::Plain;
let mut type_anchor = None;
let mut segments = Vec::new();
let mut generic_args = Vec::new();
#[cfg(test)]
let mut ast_segments = Vec::new();
#[cfg(test)]
let mut ast_segments_offset = 0;
#[allow(unused_mut)]
let mut push_segment = |_segment: &ast::PathSegment, segments: &mut Vec<Name>, name| {
#[cfg(test)]
ast_segments.push(_segment.clone());
segments.push(name);
};
loop {
let segment = path.segment()?;
@ -34,6 +52,10 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
match segment.kind()? {
ast::PathSegmentKind::Name(name_ref) => {
if name_ref.text() == "$crate" {
if path.qualifier().is_some() {
// FIXME: Report an error.
return None;
}
break kind = resolve_crate_root(
ctx.db.upcast(),
ctx.span_map().span_for_range(name_ref.syntax().text_range()).ctx,
@ -48,7 +70,7 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
.or_else(|| {
lower_generic_args_from_fn_path(
ctx,
segment.param_list(),
segment.parenthesized_arg_list(),
segment.ret_type(),
)
});
@ -56,10 +78,10 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
generic_args.resize(segments.len(), None);
generic_args.push(args);
}
segments.push(name);
push_segment(&segment, &mut segments, name);
}
ast::PathSegmentKind::SelfTypeKw => {
segments.push(Name::new_symbol_root(sym::Self_.clone()));
push_segment(&segment, &mut segments, Name::new_symbol_root(sym::Self_.clone()));
}
ast::PathSegmentKind::Type { type_ref, trait_ref } => {
assert!(path.qualifier().is_none()); // this can only occur at the first segment
@ -81,6 +103,10 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
kind = mod_path.kind;
segments.extend(mod_path.segments().iter().cloned().rev());
#[cfg(test)]
{
ast_segments_offset = mod_path.segments().len();
}
if let Some(path_generic_args) = path_generic_args {
generic_args.resize(segments.len() - num_segments, None);
generic_args.extend(Vec::from(path_generic_args).into_iter().rev());
@ -112,10 +138,18 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
}
}
ast::PathSegmentKind::CrateKw => {
if path.qualifier().is_some() {
// FIXME: Report an error.
return None;
}
kind = PathKind::Crate;
break;
}
ast::PathSegmentKind::SelfKw => {
if path.qualifier().is_some() {
// FIXME: Report an error.
return None;
}
// don't break out if `self` is the last segment of a path, this mean we got a
// use tree like `foo::{self}` which we want to resolve as `foo`
if !segments.is_empty() {
@ -162,6 +196,13 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
}
}
#[cfg(test)]
{
ast_segments.reverse();
SEGMENT_LOWERING_MAP
.with_borrow_mut(|map| map.extend(ast_segments.into_iter().zip(ast_segments_offset..)));
}
let mod_path = Interned::new(ModPath::from_segments(kind, segments));
if type_anchor.is_none() && generic_args.is_empty() {
return Some(Path::BarePath(mod_path));
@ -181,6 +222,41 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
}
}
/// This function finds the AST segment that corresponds to the HIR segment
/// with index `segment_idx` on the path that is lowered from `path`.
pub fn hir_segment_to_ast_segment(path: &ast::Path, segment_idx: u32) -> Option<ast::PathSegment> {
// Too tightly coupled to `lower_path()`, but unfortunately we cannot decouple them,
// as keeping source maps for all paths segments will have a severe impact on memory usage.
let mut segments = path.segments();
if let Some(ast::PathSegmentKind::Type { trait_ref: Some(trait_ref), .. }) =
segments.clone().next().and_then(|it| it.kind())
{
segments.next();
return find_segment(trait_ref.path()?.segments().chain(segments), segment_idx);
}
return find_segment(segments, segment_idx);
fn find_segment(
segments: impl Iterator<Item = ast::PathSegment>,
segment_idx: u32,
) -> Option<ast::PathSegment> {
segments
.filter(|segment| match segment.kind() {
Some(
ast::PathSegmentKind::CrateKw
| ast::PathSegmentKind::SelfKw
| ast::PathSegmentKind::SuperKw
| ast::PathSegmentKind::Type { .. },
)
| None => false,
Some(ast::PathSegmentKind::Name(name)) => name.text() != "$crate",
Some(ast::PathSegmentKind::SelfTypeKw) => true,
})
.nth(segment_idx as usize)
}
}
pub(super) fn lower_generic_args(
lower_ctx: &mut LowerCtx<'_>,
node: ast::GenericArgList,
@ -247,12 +323,12 @@ pub(super) fn lower_generic_args(
/// -> Z` (which desugars to `Fn<(X, Y), Output=Z>`).
fn lower_generic_args_from_fn_path(
ctx: &mut LowerCtx<'_>,
params: Option<ast::ParamList>,
args: Option<ast::ParenthesizedArgList>,
ret_type: Option<ast::RetType>,
) -> Option<GenericArgs> {
let params = params?;
let params = args?;
let mut param_types = Vec::new();
for param in params.params() {
for param in params.type_args() {
let type_ref = TypeRef::from_ast_opt(ctx, param.ty());
param_types.push(type_ref);
}

View file

@ -0,0 +1,126 @@
use expect_test::{expect, Expect};
use span::Edition;
use syntax::ast::{self, make};
use test_fixture::WithFixture;
use crate::{
lower::LowerCtx,
path::{
lower::{hir_segment_to_ast_segment, SEGMENT_LOWERING_MAP},
Path,
},
pretty,
test_db::TestDB,
type_ref::{TypesMap, TypesSourceMap},
};
fn lower_path(path: ast::Path) -> (TestDB, TypesMap, Option<Path>) {
let (db, file_id) = TestDB::with_single_file("");
let mut types_map = TypesMap::default();
let mut types_source_map = TypesSourceMap::default();
let mut ctx = LowerCtx::new(&db, file_id.into(), &mut types_map, &mut types_source_map);
let lowered_path = ctx.lower_path(path);
(db, types_map, lowered_path)
}
#[track_caller]
fn check_hir_to_ast(path: &str, ignore_segments: &[&str]) {
let path = make::path_from_text(path);
SEGMENT_LOWERING_MAP.with_borrow_mut(|map| map.clear());
let _ = lower_path(path.clone()).2.expect("failed to lower path");
SEGMENT_LOWERING_MAP.with_borrow(|map| {
for (segment, segment_idx) in map {
if ignore_segments.contains(&&*segment.to_string()) {
continue;
}
let restored_segment = hir_segment_to_ast_segment(&path, *segment_idx as u32)
.unwrap_or_else(|| {
panic!(
"failed to map back segment `{segment}` \
numbered {segment_idx} in HIR from path `{path}`"
)
});
assert_eq!(
segment, &restored_segment,
"mapping back `{segment}` numbered {segment_idx} in HIR \
from path `{path}` produced incorrect segment `{restored_segment}`"
);
}
});
}
#[test]
fn hir_to_ast_trait_ref() {
check_hir_to_ast("<A as B::C::D>::E::F", &["A"]);
}
#[test]
fn hir_to_ast_plain_path() {
check_hir_to_ast("A::B::C::D::E::F", &[]);
}
#[test]
fn hir_to_ast_crate_path() {
check_hir_to_ast("crate::A::B::C", &[]);
check_hir_to_ast("crate::super::super::A::B::C", &[]);
}
#[test]
fn hir_to_ast_self_path() {
check_hir_to_ast("self::A::B::C", &[]);
check_hir_to_ast("self::super::super::A::B::C", &[]);
}
#[test]
fn hir_to_ast_super_path() {
check_hir_to_ast("super::A::B::C", &[]);
check_hir_to_ast("super::super::super::A::B::C", &[]);
}
#[test]
fn hir_to_ast_type_anchor_path() {
check_hir_to_ast("<A::B>::C::D", &["A", "B"]);
}
#[test]
fn hir_to_ast_path_super_in_middle() {
check_hir_to_ast("A::super::B::super::super::C::D", &[]);
}
#[track_caller]
fn check_fail_lowering(path: &str) {
let (_, _, lowered_path) = lower_path(make::path_from_text(path));
assert!(lowered_path.is_none(), "path `{path}` should fail lowering");
}
#[test]
fn keywords_in_middle_fail_lowering1() {
check_fail_lowering("self::A::self::B::super::C::crate::D");
}
#[test]
fn keywords_in_middle_fail_lowering2() {
check_fail_lowering("A::super::self::C::D");
}
#[test]
fn keywords_in_middle_fail_lowering3() {
check_fail_lowering("A::crate::B::C::D");
}
#[track_caller]
fn check_path_lowering(path: &str, expected: Expect) {
let (db, types_map, lowered_path) = lower_path(make::path_from_text(path));
let lowered_path = lowered_path.expect("failed to lower path");
let mut buf = String::new();
pretty::print_path(&db, &lowered_path, &types_map, &mut buf, Edition::CURRENT)
.expect("failed to pretty-print path");
expected.assert_eq(&buf);
}
#[test]
fn fn_like_path_with_coloncolon() {
check_path_lowering("Fn::(A, B) -> C", expect![[r#"Fn::<(A, B), Output = C>"#]]);
check_path_lowering("Fn::(A, B)", expect![[r#"Fn::<(A, B), Output = ()>"#]]);
}

View file

@ -271,7 +271,7 @@ pub(crate) fn print_type_bounds(
TraitBoundModifier::None => (),
TraitBoundModifier::Maybe => write!(buf, "?")?,
}
print_path(db, path, map, buf, edition)?;
print_path(db, &map[*path], map, buf, edition)?;
}
TypeBound::ForLifetime(lifetimes, path) => {
write!(
@ -279,7 +279,7 @@ pub(crate) fn print_type_bounds(
"for<{}> ",
lifetimes.iter().map(|it| it.display(db.upcast(), edition)).format(", ")
)?;
print_path(db, path, map, buf, edition)?;
print_path(db, &map[*path], map, buf, edition)?;
}
TypeBound::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast(), edition))?,
TypeBound::Use(args) => {

View file

@ -576,10 +576,12 @@ impl Resolver {
match scope {
Scope::BlockScope(m) => traits.extend(m.def_map[m.module_id].scope.traits()),
&Scope::ImplDefScope(impl_) => {
if let Some(target_trait) = &db.impl_data(impl_).target_trait {
if let Some(TypeNs::TraitId(trait_)) =
self.resolve_path_in_type_ns_fully(db, &target_trait.path)
{
let impl_data = db.impl_data(impl_);
if let Some(target_trait) = impl_data.target_trait {
if let Some(TypeNs::TraitId(trait_)) = self.resolve_path_in_type_ns_fully(
db,
&impl_data.types_map[target_trait.path],
) {
traits.insert(trait_);
}
}
@ -640,9 +642,9 @@ impl Resolver {
})
}
pub fn generic_params(&self) -> Option<&Arc<GenericParams>> {
pub fn generic_params(&self) -> Option<&GenericParams> {
self.scopes().find_map(|scope| match scope {
Scope::GenericParams { params, .. } => Some(params),
Scope::GenericParams { params, .. } => Some(&**params),
_ => None,
})
}

View file

@ -78,6 +78,19 @@ impl FileLoader for TestDB {
}
impl TestDB {
pub(crate) fn fetch_test_crate(&self) -> CrateId {
let crate_graph = self.crate_graph();
let it = crate_graph
.iter()
.find(|&idx| {
crate_graph[idx].display_name.as_ref().map(|it| it.canonical_name().as_str())
== Some("ra_test_fixture")
})
.or_else(|| crate_graph.iter().next())
.unwrap();
it
}
pub(crate) fn module_for_file(&self, file_id: FileId) -> ModuleId {
for &krate in self.relevant_crates(file_id).iter() {
let crate_def_map = self.crate_def_map(krate);

View file

@ -180,6 +180,13 @@ impl<FileId: FileIdToSyntax, T> InFileWrapper<FileId, T> {
}
}
#[allow(private_bounds)]
impl<FileId: FileIdToSyntax, N: AstNode> InFileWrapper<FileId, AstPtr<N>> {
pub fn to_node(&self, db: &dyn ExpandDatabase) -> N {
self.value.to_node(&self.file_syntax(db))
}
}
impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, N> {
pub fn syntax(&self) -> InFileWrapper<FileId, &SyntaxNode> {
self.with_value(self.value.syntax())

View file

@ -110,7 +110,8 @@ pub(crate) fn fixup_syntax(
}
},
ast::ExprStmt(it) => {
if it.semicolon_token().is_none() {
let needs_semi = it.semicolon_token().is_none() && it.expr().map_or(false, |e| e.syntax().kind() != SyntaxKind::BLOCK_EXPR);
if needs_semi {
append.insert(node.clone().into(), vec![
Leaf::Punct(Punct {
char: ';',
@ -905,6 +906,21 @@ fn foo() {
"#,
expect![[r#"
fn foo () {|| __ra_fixup}
"#]],
);
}
#[test]
fn fixup_regression_() {
check(
r#"
fn foo() {
{}
{}
}
"#,
expect![[r#"
fn foo () {{} {}}
"#]],
);
}

View file

@ -269,6 +269,13 @@ pub enum MacroDefKind {
ProcMacro(AstId<ast::Fn>, CustomProcMacroExpander, ProcMacroKind),
}
impl MacroDefKind {
#[inline]
pub fn is_declarative(&self) -> bool {
matches!(self, MacroDefKind::Declarative(..))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct EagerCallInfo {
/// The expanded argument of the eager macro.

View file

@ -22,7 +22,7 @@ use crate::{
consteval::ConstEvalError,
dyn_compatibility::DynCompatibilityViolation,
layout::{Layout, LayoutError},
lower::{GenericDefaults, GenericPredicates},
lower::{Diagnostics, GenericDefaults, GenericPredicates},
method_resolution::{InherentImpls, TraitImpls, TyFingerprint},
mir::{BorrowckResult, MirBody, MirLowerError},
Binders, ClosureId, Const, FnDefId, ImplTraitId, ImplTraits, InferenceResult, Interner,
@ -115,21 +115,35 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
#[ra_salsa::cycle(crate::lower::ty_recover)]
fn ty(&self, def: TyDefId) -> Binders<Ty>;
#[ra_salsa::invoke(crate::lower::type_for_type_alias_with_diagnostics_query)]
fn type_for_type_alias_with_diagnostics(&self, def: TypeAliasId) -> (Binders<Ty>, Diagnostics);
/// Returns the type of the value of the given constant, or `None` if the `ValueTyDefId` is
/// a `StructId` or `EnumVariantId` with a record constructor.
#[ra_salsa::invoke(crate::lower::value_ty_query)]
fn value_ty(&self, def: ValueTyDefId) -> Option<Binders<Ty>>;
#[ra_salsa::invoke(crate::lower::impl_self_ty_with_diagnostics_query)]
#[ra_salsa::cycle(crate::lower::impl_self_ty_with_diagnostics_recover)]
fn impl_self_ty_with_diagnostics(&self, def: ImplId) -> (Binders<Ty>, Diagnostics);
#[ra_salsa::invoke(crate::lower::impl_self_ty_query)]
#[ra_salsa::cycle(crate::lower::impl_self_ty_recover)]
fn impl_self_ty(&self, def: ImplId) -> Binders<Ty>;
#[ra_salsa::invoke(crate::lower::const_param_ty_with_diagnostics_query)]
fn const_param_ty_with_diagnostics(&self, def: ConstParamId) -> (Ty, Diagnostics);
#[ra_salsa::invoke(crate::lower::const_param_ty_query)]
fn const_param_ty(&self, def: ConstParamId) -> Ty;
#[ra_salsa::invoke(crate::lower::impl_trait_with_diagnostics_query)]
fn impl_trait_with_diagnostics(&self, def: ImplId) -> Option<(Binders<TraitRef>, Diagnostics)>;
#[ra_salsa::invoke(crate::lower::impl_trait_query)]
fn impl_trait(&self, def: ImplId) -> Option<Binders<TraitRef>>;
#[ra_salsa::invoke(crate::lower::field_types_with_diagnostics_query)]
fn field_types_with_diagnostics(
&self,
var: VariantId,
) -> (Arc<ArenaMap<LocalFieldId, Binders<Ty>>>, Diagnostics);
#[ra_salsa::invoke(crate::lower::field_types_query)]
fn field_types(&self, var: VariantId) -> Arc<ArenaMap<LocalFieldId, Binders<Ty>>>;
@ -154,6 +168,11 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
#[ra_salsa::invoke(crate::lower::generic_predicates_query)]
fn generic_predicates(&self, def: GenericDefId) -> GenericPredicates;
#[ra_salsa::invoke(crate::lower::generic_predicates_without_parent_with_diagnostics_query)]
fn generic_predicates_without_parent_with_diagnostics(
&self,
def: GenericDefId,
) -> (GenericPredicates, Diagnostics);
#[ra_salsa::invoke(crate::lower::generic_predicates_without_parent_query)]
fn generic_predicates_without_parent(&self, def: GenericDefId) -> GenericPredicates;
@ -164,8 +183,13 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
#[ra_salsa::invoke(crate::lower::trait_environment_query)]
fn trait_environment(&self, def: GenericDefId) -> Arc<TraitEnvironment>;
#[ra_salsa::invoke(crate::lower::generic_defaults_with_diagnostics_query)]
#[ra_salsa::cycle(crate::lower::generic_defaults_with_diagnostics_recover)]
fn generic_defaults_with_diagnostics(
&self,
def: GenericDefId,
) -> (GenericDefaults, Diagnostics);
#[ra_salsa::invoke(crate::lower::generic_defaults_query)]
#[ra_salsa::cycle(crate::lower::generic_defaults_recover)]
fn generic_defaults(&self, def: GenericDefId) -> GenericDefaults;
#[ra_salsa::invoke(InherentImpls::inherent_impls_in_crate_query)]

View file

@ -9,5 +9,5 @@ pub use crate::diagnostics::{
expr::{
record_literal_missing_fields, record_pattern_missing_fields, BodyValidationDiagnostic,
},
unsafe_check::{missing_unsafe, unsafe_expressions, UnsafeExpr},
unsafe_check::{missing_unsafe, unsafe_expressions, InsideUnsafeBlock, UnsafetyReason},
};

View file

@ -383,9 +383,6 @@ impl<'db> PatCx for MatchCheckCtx<'db> {
} else {
let variant = Self::variant_id_for_adt(self.db, ctor, adt).unwrap();
// Whether we must not match the fields of this variant exhaustively.
let is_non_exhaustive =
LazyCell::new(|| self.is_foreign_non_exhaustive(adt));
let visibilities = LazyCell::new(|| self.db.field_visibilities(variant));
self.list_variant_fields(ty, variant)
@ -396,8 +393,7 @@ impl<'db> PatCx for MatchCheckCtx<'db> {
.is_visible_from(self.db.upcast(), self.module)
};
let is_uninhabited = self.is_uninhabited(&ty);
let private_uninhabited =
is_uninhabited && (!is_visible() || *is_non_exhaustive);
let private_uninhabited = is_uninhabited && !is_visible();
(ty, PrivateUninhabitedField(private_uninhabited))
})
.collect()

View file

@ -1,12 +1,16 @@
//! Provides validations for unsafe code. Currently checks if unsafe functions are missing
//! unsafe blocks.
use std::mem;
use either::Either;
use hir_def::{
body::Body,
hir::{Expr, ExprId, ExprOrPatId, Pat, UnaryOp},
resolver::{resolver_for_expr, ResolveValueResult, Resolver, ValueNs},
hir::{Expr, ExprId, ExprOrPatId, Pat, PatId, Statement, UnaryOp},
path::Path,
resolver::{HasResolver, ResolveValueResult, Resolver, ValueNs},
type_ref::Rawness,
DefWithBodyId,
AdtId, DefWithBodyId, FieldId, VariantId,
};
use crate::{
@ -16,7 +20,10 @@ use crate::{
/// Returns `(unsafe_exprs, fn_is_unsafe)`.
///
/// If `fn_is_unsafe` is false, `unsafe_exprs` are hard errors. If true, they're `unsafe_op_in_unsafe_fn`.
pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> (Vec<ExprOrPatId>, bool) {
pub fn missing_unsafe(
db: &dyn HirDatabase,
def: DefWithBodyId,
) -> (Vec<(ExprOrPatId, UnsafetyReason)>, bool) {
let _p = tracing::info_span!("missing_unsafe").entered();
let mut res = Vec::new();
@ -30,111 +37,243 @@ pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> (Vec<ExprOrPa
let body = db.body(def);
let infer = db.infer(def);
unsafe_expressions(db, &infer, def, &body, body.body_expr, &mut |expr| {
if !expr.inside_unsafe_block {
res.push(expr.node);
let mut callback = |node, inside_unsafe_block, reason| {
if inside_unsafe_block == InsideUnsafeBlock::No {
res.push((node, reason));
}
});
};
let mut visitor = UnsafeVisitor::new(db, &infer, &body, def, &mut callback);
visitor.walk_expr(body.body_expr);
if !is_unsafe {
// Unsafety in function parameter patterns (that can only be union destructuring)
// cannot be inserted into an unsafe block, so even with `unsafe_op_in_unsafe_fn`
// it is turned off for unsafe functions.
for &param in &body.params {
visitor.walk_pat(param);
}
}
(res, is_unsafe)
}
pub struct UnsafeExpr {
pub node: ExprOrPatId,
pub inside_unsafe_block: bool,
#[derive(Debug, Clone, Copy)]
pub enum UnsafetyReason {
UnionField,
UnsafeFnCall,
InlineAsm,
RawPtrDeref,
MutableStatic,
ExternStatic,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InsideUnsafeBlock {
No,
Yes,
}
// FIXME: Move this out, its not a diagnostic only thing anymore, and handle unsafe pattern accesses as well
pub fn unsafe_expressions(
db: &dyn HirDatabase,
infer: &InferenceResult,
def: DefWithBodyId,
body: &Body,
current: ExprId,
unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr),
unsafe_expr_cb: &mut dyn FnMut(ExprOrPatId, InsideUnsafeBlock, UnsafetyReason),
) {
walk_unsafe(
db,
infer,
body,
&mut resolver_for_expr(db.upcast(), def, current),
def,
current,
false,
unsafe_expr_cb,
)
let mut visitor = UnsafeVisitor::new(db, infer, body, def, unsafe_expr_cb);
_ = visitor.resolver.update_to_inner_scope(db.upcast(), def, current);
visitor.walk_expr(current);
}
fn walk_unsafe(
db: &dyn HirDatabase,
infer: &InferenceResult,
body: &Body,
resolver: &mut Resolver,
struct UnsafeVisitor<'a> {
db: &'a dyn HirDatabase,
infer: &'a InferenceResult,
body: &'a Body,
resolver: Resolver,
def: DefWithBodyId,
current: ExprId,
inside_unsafe_block: bool,
unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr),
) {
let mut mark_unsafe_path = |path, node| {
let g = resolver.update_to_inner_scope(db.upcast(), def, current);
let hygiene = body.expr_or_pat_path_hygiene(node);
let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path, hygiene);
if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial {
let static_data = db.static_data(id);
if static_data.mutable || (static_data.is_extern && !static_data.has_safe_kw) {
unsafe_expr_cb(UnsafeExpr { node, inside_unsafe_block });
}
}
resolver.reset_to_guard(g);
};
inside_unsafe_block: InsideUnsafeBlock,
inside_assignment: bool,
inside_union_destructure: bool,
unsafe_expr_cb: &'a mut dyn FnMut(ExprOrPatId, InsideUnsafeBlock, UnsafetyReason),
}
let expr = &body.exprs[current];
match expr {
&Expr::Call { callee, .. } => {
if let Some(func) = infer[callee].as_fn_def(db) {
if is_fn_unsafe_to_call(db, func) {
unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block });
}
}
impl<'a> UnsafeVisitor<'a> {
fn new(
db: &'a dyn HirDatabase,
infer: &'a InferenceResult,
body: &'a Body,
def: DefWithBodyId,
unsafe_expr_cb: &'a mut dyn FnMut(ExprOrPatId, InsideUnsafeBlock, UnsafetyReason),
) -> Self {
let resolver = def.resolver(db.upcast());
Self {
db,
infer,
body,
resolver,
def,
inside_unsafe_block: InsideUnsafeBlock::No,
inside_assignment: false,
inside_union_destructure: false,
unsafe_expr_cb,
}
Expr::Path(path) => mark_unsafe_path(path, current.into()),
Expr::Ref { expr, rawness: Rawness::RawPtr, mutability: _ } => {
if let Expr::Path(_) = body.exprs[*expr] {
// Do not report unsafe for `addr_of[_mut]!(EXTERN_OR_MUT_STATIC)`,
// see https://github.com/rust-lang/rust/pull/125834.
return;
}
}
Expr::MethodCall { .. } => {
if infer
.method_resolution(current)
.map(|(func, _)| is_fn_unsafe_to_call(db, func))
.unwrap_or(false)
{
unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block });
}
}
Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
if let TyKind::Raw(..) = &infer[*expr].kind(Interner) {
unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block });
}
}
Expr::Unsafe { .. } => {
return body.walk_child_exprs(current, |child| {
walk_unsafe(db, infer, body, resolver, def, child, true, unsafe_expr_cb);
});
}
&Expr::Assignment { target, value: _ } => {
body.walk_pats(target, &mut |pat| {
if let Pat::Path(path) = &body[pat] {
mark_unsafe_path(path, pat.into());
}
});
}
_ => {}
}
body.walk_child_exprs(current, |child| {
walk_unsafe(db, infer, body, resolver, def, child, inside_unsafe_block, unsafe_expr_cb);
});
fn call_cb(&mut self, node: ExprOrPatId, reason: UnsafetyReason) {
(self.unsafe_expr_cb)(node, self.inside_unsafe_block, reason);
}
fn walk_pats_top(&mut self, pats: impl Iterator<Item = PatId>, parent_expr: ExprId) {
let guard = self.resolver.update_to_inner_scope(self.db.upcast(), self.def, parent_expr);
pats.for_each(|pat| self.walk_pat(pat));
self.resolver.reset_to_guard(guard);
}
fn walk_pat(&mut self, current: PatId) {
let pat = &self.body.pats[current];
if self.inside_union_destructure {
match pat {
Pat::Tuple { .. }
| Pat::Record { .. }
| Pat::Range { .. }
| Pat::Slice { .. }
| Pat::Path(..)
| Pat::Lit(..)
| Pat::Bind { .. }
| Pat::TupleStruct { .. }
| Pat::Ref { .. }
| Pat::Box { .. }
| Pat::Expr(..)
| Pat::ConstBlock(..) => self.call_cb(current.into(), UnsafetyReason::UnionField),
// `Or` only wraps other patterns, and `Missing`/`Wild` do not constitute a read.
Pat::Missing | Pat::Wild | Pat::Or(_) => {}
}
}
match pat {
Pat::Record { .. } => {
if let Some((AdtId::UnionId(_), _)) = self.infer[current].as_adt() {
let old_inside_union_destructure =
mem::replace(&mut self.inside_union_destructure, true);
self.body.walk_pats_shallow(current, |pat| self.walk_pat(pat));
self.inside_union_destructure = old_inside_union_destructure;
return;
}
}
Pat::Path(path) => self.mark_unsafe_path(current.into(), path),
&Pat::ConstBlock(expr) => {
let old_inside_assignment = mem::replace(&mut self.inside_assignment, false);
self.walk_expr(expr);
self.inside_assignment = old_inside_assignment;
}
&Pat::Expr(expr) => self.walk_expr(expr),
_ => {}
}
self.body.walk_pats_shallow(current, |pat| self.walk_pat(pat));
}
fn walk_expr(&mut self, current: ExprId) {
let expr = &self.body.exprs[current];
let inside_assignment = mem::replace(&mut self.inside_assignment, false);
match expr {
&Expr::Call { callee, .. } => {
if let Some(func) = self.infer[callee].as_fn_def(self.db) {
if is_fn_unsafe_to_call(self.db, func) {
self.call_cb(current.into(), UnsafetyReason::UnsafeFnCall);
}
}
}
Expr::Path(path) => {
let guard =
self.resolver.update_to_inner_scope(self.db.upcast(), self.def, current);
self.mark_unsafe_path(current.into(), path);
self.resolver.reset_to_guard(guard);
}
Expr::Ref { expr, rawness: Rawness::RawPtr, mutability: _ } => {
if let Expr::Path(_) = self.body.exprs[*expr] {
// Do not report unsafe for `addr_of[_mut]!(EXTERN_OR_MUT_STATIC)`,
// see https://github.com/rust-lang/rust/pull/125834.
return;
}
}
Expr::MethodCall { .. } => {
if self
.infer
.method_resolution(current)
.map(|(func, _)| is_fn_unsafe_to_call(self.db, func))
.unwrap_or(false)
{
self.call_cb(current.into(), UnsafetyReason::UnsafeFnCall);
}
}
Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
if let TyKind::Raw(..) = &self.infer[*expr].kind(Interner) {
self.call_cb(current.into(), UnsafetyReason::RawPtrDeref);
}
}
Expr::Unsafe { .. } => {
let old_inside_unsafe_block =
mem::replace(&mut self.inside_unsafe_block, InsideUnsafeBlock::Yes);
self.body.walk_child_exprs_without_pats(current, |child| self.walk_expr(child));
self.inside_unsafe_block = old_inside_unsafe_block;
return;
}
&Expr::Assignment { target, value: _ } => {
let old_inside_assignment = mem::replace(&mut self.inside_assignment, true);
self.walk_pats_top(std::iter::once(target), current);
self.inside_assignment = old_inside_assignment;
}
Expr::InlineAsm(_) => self.call_cb(current.into(), UnsafetyReason::InlineAsm),
// rustc allows union assignment to propagate through field accesses and casts.
Expr::Cast { .. } => self.inside_assignment = inside_assignment,
Expr::Field { .. } => {
self.inside_assignment = inside_assignment;
if !inside_assignment {
if let Some(Either::Left(FieldId { parent: VariantId::UnionId(_), .. })) =
self.infer.field_resolution(current)
{
self.call_cb(current.into(), UnsafetyReason::UnionField);
}
}
}
Expr::Block { statements, .. } | Expr::Async { statements, .. } => {
self.walk_pats_top(
statements.iter().filter_map(|statement| match statement {
&Statement::Let { pat, .. } => Some(pat),
_ => None,
}),
current,
);
}
Expr::Match { arms, .. } => {
self.walk_pats_top(arms.iter().map(|arm| arm.pat), current);
}
&Expr::Let { pat, .. } => {
self.walk_pats_top(std::iter::once(pat), current);
}
Expr::Closure { args, .. } => {
self.walk_pats_top(args.iter().copied(), current);
}
_ => {}
}
self.body.walk_child_exprs_without_pats(current, |child| self.walk_expr(child));
}
fn mark_unsafe_path(&mut self, node: ExprOrPatId, path: &Path) {
let hygiene = self.body.expr_or_pat_path_hygiene(node);
let value_or_partial =
self.resolver.resolve_path_in_value_ns(self.db.upcast(), path, hygiene);
if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial {
let static_data = self.db.static_data(id);
if static_data.mutable {
self.call_cb(node, UnsafetyReason::MutableStatic);
} else if static_data.is_extern && !static_data.has_safe_kw {
self.call_cb(node, UnsafetyReason::ExternStatic);
}
}
}
}

View file

@ -1047,10 +1047,14 @@ impl HirDisplay for Ty {
);
// We print all params except implicit impl Trait params. Still a bit weird; should we leave out parent and self?
if parameters.len() - impl_ > 0 {
let params_len = parameters.len();
// `parameters` are in the order of fn's params (including impl traits), fn's lifetimes
let parameters =
generic_args_sans_defaults(f, Some(generic_def_id), parameters);
let without_impl = self_param as usize + type_ + const_ + lifetime;
assert!(params_len >= parameters.len());
let defaults = params_len - parameters.len();
let without_impl =
self_param as usize + type_ + const_ + lifetime - defaults;
// parent's params (those from enclosing impl or trait, if any).
let (fn_params, parent_params) = parameters.split_at(without_impl + impl_);
@ -2062,12 +2066,12 @@ impl HirDisplayWithTypesMap for TypeBound {
types_map: &TypesMap,
) -> Result<(), HirDisplayError> {
match self {
TypeBound::Path(path, modifier) => {
&TypeBound::Path(path, modifier) => {
match modifier {
TraitBoundModifier::None => (),
TraitBoundModifier::Maybe => write!(f, "?")?,
}
path.hir_fmt(f, types_map)
types_map[path].hir_fmt(f, types_map)
}
TypeBound::Lifetime(lifetime) => {
write!(f, "{}", lifetime.name.display(f.db.upcast(), f.edition()))
@ -2079,7 +2083,7 @@ impl HirDisplayWithTypesMap for TypeBound {
"for<{}> ",
lifetimes.iter().map(|it| it.display(f.db.upcast(), edition)).format(", ")
)?;
path.hir_fmt(f, types_map)
types_map[*path].hir_fmt(f, types_map)
}
TypeBound::Use(args) => {
let edition = f.edition();

View file

@ -55,6 +55,10 @@ impl Generics {
self.def
}
pub(crate) fn self_types_map(&self) -> &TypesMap {
&self.params.types_map
}
pub(crate) fn iter_id(&self) -> impl Iterator<Item = GenericParamId> + '_ {
self.iter_self_id().chain(self.iter_parent_id())
}
@ -86,15 +90,13 @@ impl Generics {
self.iter_self().chain(self.iter_parent())
}
pub(crate) fn iter_with_types_map(
pub(crate) fn iter_parents_with_types_map(
&self,
) -> impl Iterator<Item = ((GenericParamId, GenericParamDataRef<'_>), &TypesMap)> + '_ {
self.iter_self().zip(std::iter::repeat(&self.params.types_map)).chain(
self.iter_parent().zip(
self.parent_generics()
.into_iter()
.flat_map(|it| std::iter::repeat(&it.params.types_map)),
),
self.iter_parent().zip(
self.parent_generics()
.into_iter()
.flat_map(|it| std::iter::repeat(&it.params.types_map)),
)
}

View file

@ -58,7 +58,7 @@ use crate::{
fold_tys,
generics::Generics,
infer::{coerce::CoerceMany, expr::ExprIsRead, unify::InferenceTable},
lower::ImplTraitLoweringMode,
lower::{ImplTraitLoweringMode, TyLoweringDiagnostic},
mir::MirSpan,
to_assoc_type_id,
traits::FnTrait,
@ -191,6 +191,14 @@ impl<T> InferOk<T> {
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum InferenceTyDiagnosticSource {
/// Diagnostics that come from types in the body.
Body,
/// Diagnostics that come from types in fn parameters/return type, or static & const types.
Signature,
}
#[derive(Debug)]
pub(crate) struct TypeError;
pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>;
@ -264,6 +272,10 @@ pub enum InferenceDiagnostic {
expr_ty: Ty,
cast_ty: Ty,
},
TyDiagnostic {
source: InferenceTyDiagnosticSource,
diag: TyLoweringDiagnostic,
},
}
/// A mismatch between an expected and an inferred type.
@ -858,7 +870,8 @@ impl<'a> InferenceContext<'a> {
}
fn collect_const(&mut self, data: &ConstData) {
let return_ty = self.make_ty(data.type_ref, &data.types_map);
let return_ty =
self.make_ty(data.type_ref, &data.types_map, InferenceTyDiagnosticSource::Signature);
// Constants might be defining usage sites of TAITs.
self.make_tait_coercion_table(iter::once(&return_ty));
@ -867,7 +880,8 @@ impl<'a> InferenceContext<'a> {
}
fn collect_static(&mut self, data: &StaticData) {
let return_ty = self.make_ty(data.type_ref, &data.types_map);
let return_ty =
self.make_ty(data.type_ref, &data.types_map, InferenceTyDiagnosticSource::Signature);
// Statics might be defining usage sites of TAITs.
self.make_tait_coercion_table(iter::once(&return_ty));
@ -877,11 +891,12 @@ impl<'a> InferenceContext<'a> {
fn collect_fn(&mut self, func: FunctionId) {
let data = self.db.function_data(func);
let mut param_tys = self.with_ty_lowering(&data.types_map, |ctx| {
ctx.type_param_mode(ParamLoweringMode::Placeholder)
.impl_trait_mode(ImplTraitLoweringMode::Param);
data.params.iter().map(|&type_ref| ctx.lower_ty(type_ref)).collect::<Vec<_>>()
});
let mut param_tys =
self.with_ty_lowering(&data.types_map, InferenceTyDiagnosticSource::Signature, |ctx| {
ctx.type_param_mode(ParamLoweringMode::Placeholder)
.impl_trait_mode(ImplTraitLoweringMode::Param);
data.params.iter().map(|&type_ref| ctx.lower_ty(type_ref)).collect::<Vec<_>>()
});
// Check if function contains a va_list, if it does then we append it to the parameter types
// that are collected from the function data
if data.is_varargs() {
@ -918,11 +933,12 @@ impl<'a> InferenceContext<'a> {
}
let return_ty = data.ret_type;
let return_ty = self.with_ty_lowering(&data.types_map, |ctx| {
ctx.type_param_mode(ParamLoweringMode::Placeholder)
.impl_trait_mode(ImplTraitLoweringMode::Opaque)
.lower_ty(return_ty)
});
let return_ty =
self.with_ty_lowering(&data.types_map, InferenceTyDiagnosticSource::Signature, |ctx| {
ctx.type_param_mode(ParamLoweringMode::Placeholder)
.impl_trait_mode(ImplTraitLoweringMode::Opaque)
.lower_ty(return_ty)
});
let return_ty = self.insert_type_vars(return_ty);
let return_ty = if let Some(rpits) = self.db.return_type_impl_traits(func) {
@ -1226,9 +1242,20 @@ impl<'a> InferenceContext<'a> {
self.result.diagnostics.push(diagnostic);
}
fn push_ty_diagnostics(
&mut self,
source: InferenceTyDiagnosticSource,
diagnostics: Vec<TyLoweringDiagnostic>,
) {
self.result.diagnostics.extend(
diagnostics.into_iter().map(|diag| InferenceDiagnostic::TyDiagnostic { source, diag }),
);
}
fn with_ty_lowering<R>(
&self,
&mut self,
types_map: &TypesMap,
types_source: InferenceTyDiagnosticSource,
f: impl FnOnce(&mut crate::lower::TyLoweringContext<'_>) -> R,
) -> R {
let mut ctx = crate::lower::TyLoweringContext::new(
@ -1237,32 +1264,41 @@ impl<'a> InferenceContext<'a> {
types_map,
self.owner.into(),
);
f(&mut ctx)
let result = f(&mut ctx);
self.push_ty_diagnostics(types_source, ctx.diagnostics);
result
}
fn with_body_ty_lowering<R>(
&self,
&mut self,
f: impl FnOnce(&mut crate::lower::TyLoweringContext<'_>) -> R,
) -> R {
self.with_ty_lowering(&self.body.types, f)
self.with_ty_lowering(&self.body.types, InferenceTyDiagnosticSource::Body, f)
}
fn make_ty(&mut self, type_ref: TypeRefId, types_map: &TypesMap) -> Ty {
let ty = self.with_ty_lowering(types_map, |ctx| ctx.lower_ty(type_ref));
fn make_ty(
&mut self,
type_ref: TypeRefId,
types_map: &TypesMap,
type_source: InferenceTyDiagnosticSource,
) -> Ty {
let ty = self.with_ty_lowering(types_map, type_source, |ctx| ctx.lower_ty(type_ref));
let ty = self.insert_type_vars(ty);
self.normalize_associated_types_in(ty)
}
fn make_body_ty(&mut self, type_ref: TypeRefId) -> Ty {
self.make_ty(type_ref, &self.body.types)
self.make_ty(type_ref, &self.body.types, InferenceTyDiagnosticSource::Body)
}
fn err_ty(&self) -> Ty {
self.result.standard_types.unknown.clone()
}
fn make_lifetime(&mut self, lifetime_ref: &LifetimeRef) -> Lifetime {
let lt = self.with_ty_lowering(TypesMap::EMPTY, |ctx| ctx.lower_lifetime(lifetime_ref));
fn make_body_lifetime(&mut self, lifetime_ref: &LifetimeRef) -> Lifetime {
let lt = self.with_ty_lowering(TypesMap::EMPTY, InferenceTyDiagnosticSource::Body, |ctx| {
ctx.lower_lifetime(lifetime_ref)
});
self.insert_type_vars(lt)
}
@ -1431,12 +1467,20 @@ impl<'a> InferenceContext<'a> {
Some(ResolveValueResult::ValueNs(value, _)) => match value {
ValueNs::EnumVariantId(var) => {
let substs = ctx.substs_from_path(path, var.into(), true);
self.push_ty_diagnostics(
InferenceTyDiagnosticSource::Body,
ctx.diagnostics,
);
let ty = self.db.ty(var.lookup(self.db.upcast()).parent.into());
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
return (ty, Some(var.into()));
}
ValueNs::StructId(strukt) => {
let substs = ctx.substs_from_path(path, strukt.into(), true);
self.push_ty_diagnostics(
InferenceTyDiagnosticSource::Body,
ctx.diagnostics,
);
let ty = self.db.ty(strukt.into());
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
return (ty, Some(strukt.into()));
@ -1462,18 +1506,21 @@ impl<'a> InferenceContext<'a> {
return match resolution {
TypeNs::AdtId(AdtId::StructId(strukt)) => {
let substs = ctx.substs_from_path(path, strukt.into(), true);
self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics);
let ty = self.db.ty(strukt.into());
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
forbid_unresolved_segments((ty, Some(strukt.into())), unresolved)
}
TypeNs::AdtId(AdtId::UnionId(u)) => {
let substs = ctx.substs_from_path(path, u.into(), true);
self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics);
let ty = self.db.ty(u.into());
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
forbid_unresolved_segments((ty, Some(u.into())), unresolved)
}
TypeNs::EnumVariantId(var) => {
let substs = ctx.substs_from_path(path, var.into(), true);
self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics);
let ty = self.db.ty(var.lookup(self.db.upcast()).parent.into());
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
forbid_unresolved_segments((ty, Some(var.into())), unresolved)
@ -1519,6 +1566,9 @@ impl<'a> InferenceContext<'a> {
resolved_segment,
current_segment,
false,
&mut |_, _reason| {
// FIXME: Report an error.
},
);
ty = self.table.insert_type_vars(ty);
@ -1532,6 +1582,7 @@ impl<'a> InferenceContext<'a> {
remaining_idx += 1;
remaining_segments = remaining_segments.skip(1);
}
self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics);
let variant = ty.as_adt().and_then(|(id, _)| match id {
AdtId::StructId(s) => Some(VariantId::StructId(s)),
@ -1550,6 +1601,7 @@ impl<'a> InferenceContext<'a> {
};
let substs =
ctx.substs_from_path_segment(resolved_seg, Some(it.into()), true, None);
self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics);
let ty = self.db.ty(it.into());
let ty = self.insert_type_vars(ty.substitute(Interner, &substs));

View file

@ -125,7 +125,11 @@ impl CoerceMany {
// pointers to have a chance at getting a match. See
// https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916
let sig = match (self.merged_ty().kind(Interner), expr_ty.kind(Interner)) {
(TyKind::FnDef(x, _), TyKind::FnDef(y, _)) if x == y => None,
(TyKind::FnDef(x, _), TyKind::FnDef(y, _))
if x == y && ctx.table.unify(&self.merged_ty(), &expr_ty) =>
{
None
}
(TyKind::Closure(x, _), TyKind::Closure(y, _)) if x == y => None,
(TyKind::FnDef(..) | TyKind::Closure(..), TyKind::FnDef(..) | TyKind::Closure(..)) => {
// FIXME: we're ignoring safety here. To be more correct, if we have one FnDef and one Closure,

View file

@ -1287,8 +1287,8 @@ impl InferenceContext<'_> {
tgt_expr: ExprId,
) {
match fn_x {
FnTrait::FnOnce => (),
FnTrait::FnMut => {
FnTrait::FnOnce | FnTrait::AsyncFnOnce => (),
FnTrait::FnMut | FnTrait::AsyncFnMut => {
if let TyKind::Ref(Mutability::Mut, lt, inner) = derefed_callee.kind(Interner) {
if adjustments
.last()
@ -1312,7 +1312,7 @@ impl InferenceContext<'_> {
));
}
}
FnTrait::Fn => {
FnTrait::Fn | FnTrait::AsyncFn => {
if !matches!(derefed_callee.kind(Interner), TyKind::Ref(Mutability::Not, _, _)) {
adjustments.push(Adjustment::borrow(
Mutability::Not,
@ -2155,7 +2155,7 @@ impl InferenceContext<'_> {
DebruijnIndex::INNERMOST,
)
},
|this, lt_ref| this.make_lifetime(lt_ref),
|this, lt_ref| this.make_body_lifetime(lt_ref),
),
};

View file

@ -19,7 +19,7 @@ use crate::{
TyBuilder, TyExt, TyKind, ValueTyDefId,
};
use super::{ExprOrPatId, InferenceContext};
use super::{ExprOrPatId, InferenceContext, InferenceTyDiagnosticSource};
impl InferenceContext<'_> {
pub(super) fn infer_path(&mut self, path: &Path, id: ExprOrPatId) -> Option<Ty> {
@ -163,6 +163,7 @@ impl InferenceContext<'_> {
let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1);
let (ty, _) = ctx.lower_ty_relative_path(ty, orig_ns, remaining_segments_for_ty);
self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics);
let ty = self.table.insert_type_vars(ty);
let ty = self.table.normalize_associated_types_in(ty);
self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))?
@ -265,6 +266,9 @@ impl InferenceContext<'_> {
resolved_segment,
remaining_segments_for_ty,
true,
&mut |_, _reason| {
// FIXME: Report an error.
},
)
});
if ty.is_unknown() {

View file

@ -666,7 +666,7 @@ impl<'a> InferenceTable<'a> {
highest_known_var: InferenceVar,
}
impl TypeFolder<Interner> for VarFudger<'_, '_> {
fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner, Error = Self::Error> {
fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner> {
self
}
@ -794,69 +794,75 @@ impl<'a> InferenceTable<'a> {
ty: &Ty,
num_args: usize,
) -> Option<(FnTrait, Vec<Ty>, Ty)> {
let krate = self.trait_env.krate;
let fn_once_trait = FnTrait::FnOnce.get_id(self.db, krate)?;
let trait_data = self.db.trait_data(fn_once_trait);
let output_assoc_type =
trait_data.associated_type_by_name(&Name::new_symbol_root(sym::Output.clone()))?;
for (fn_trait_name, output_assoc_name, subtraits) in [
(FnTrait::FnOnce, sym::Output.clone(), &[FnTrait::Fn, FnTrait::FnMut][..]),
(FnTrait::AsyncFnMut, sym::CallRefFuture.clone(), &[FnTrait::AsyncFn]),
(FnTrait::AsyncFnOnce, sym::CallOnceFuture.clone(), &[]),
] {
let krate = self.trait_env.krate;
let fn_trait = fn_trait_name.get_id(self.db, krate)?;
let trait_data = self.db.trait_data(fn_trait);
let output_assoc_type =
trait_data.associated_type_by_name(&Name::new_symbol_root(output_assoc_name))?;
let mut arg_tys = Vec::with_capacity(num_args);
let arg_ty = TyBuilder::tuple(num_args)
.fill(|it| {
let arg = match it {
ParamKind::Type => self.new_type_var(),
ParamKind::Lifetime => unreachable!("Tuple with lifetime parameter"),
ParamKind::Const(_) => unreachable!("Tuple with const parameter"),
};
arg_tys.push(arg.clone());
arg.cast(Interner)
})
.build();
let mut arg_tys = Vec::with_capacity(num_args);
let arg_ty = TyBuilder::tuple(num_args)
.fill(|it| {
let arg = match it {
ParamKind::Type => self.new_type_var(),
ParamKind::Lifetime => unreachable!("Tuple with lifetime parameter"),
ParamKind::Const(_) => unreachable!("Tuple with const parameter"),
};
arg_tys.push(arg.clone());
arg.cast(Interner)
})
.build();
let b = TyBuilder::trait_ref(self.db, fn_once_trait);
if b.remaining() != 2 {
return None;
}
let mut trait_ref = b.push(ty.clone()).push(arg_ty).build();
let b = TyBuilder::trait_ref(self.db, fn_trait);
if b.remaining() != 2 {
return None;
}
let mut trait_ref = b.push(ty.clone()).push(arg_ty).build();
let projection = {
TyBuilder::assoc_type_projection(
let projection = TyBuilder::assoc_type_projection(
self.db,
output_assoc_type,
Some(trait_ref.substitution.clone()),
)
.build()
};
.fill_with_unknown()
.build();
let trait_env = self.trait_env.env.clone();
let obligation = InEnvironment {
goal: trait_ref.clone().cast(Interner),
environment: trait_env.clone(),
};
let canonical = self.canonicalize(obligation.clone());
if self.db.trait_solve(krate, self.trait_env.block, canonical.cast(Interner)).is_some() {
self.register_obligation(obligation.goal);
let return_ty = self.normalize_projection_ty(projection);
for fn_x in [FnTrait::Fn, FnTrait::FnMut, FnTrait::FnOnce] {
let fn_x_trait = fn_x.get_id(self.db, krate)?;
trait_ref.trait_id = to_chalk_trait_id(fn_x_trait);
let obligation: chalk_ir::InEnvironment<chalk_ir::Goal<Interner>> = InEnvironment {
goal: trait_ref.clone().cast(Interner),
environment: trait_env.clone(),
};
let canonical = self.canonicalize(obligation.clone());
if self
.db
.trait_solve(krate, self.trait_env.block, canonical.cast(Interner))
.is_some()
{
return Some((fn_x, arg_tys, return_ty));
let trait_env = self.trait_env.env.clone();
let obligation = InEnvironment {
goal: trait_ref.clone().cast(Interner),
environment: trait_env.clone(),
};
let canonical = self.canonicalize(obligation.clone());
if self.db.trait_solve(krate, self.trait_env.block, canonical.cast(Interner)).is_some()
{
self.register_obligation(obligation.goal);
let return_ty = self.normalize_projection_ty(projection);
for &fn_x in subtraits {
let fn_x_trait = fn_x.get_id(self.db, krate)?;
trait_ref.trait_id = to_chalk_trait_id(fn_x_trait);
let obligation: chalk_ir::InEnvironment<chalk_ir::Goal<Interner>> =
InEnvironment {
goal: trait_ref.clone().cast(Interner),
environment: trait_env.clone(),
};
let canonical = self.canonicalize(obligation.clone());
if self
.db
.trait_solve(krate, self.trait_env.block, canonical.cast(Interner))
.is_some()
{
return Some((fn_x, arg_tys, return_ty));
}
}
return Some((fn_trait_name, arg_tys, return_ty));
}
unreachable!("It should at least implement FnOnce at this point");
} else {
None
}
None
}
pub(super) fn insert_type_vars<T>(&mut self, ty: T) -> T
@ -1004,7 +1010,7 @@ mod resolve {
where
F: Fn(InferenceVar, VariableKind, GenericArg, DebruijnIndex) -> GenericArg,
{
fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner, Error = Self::Error> {
fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner> {
self
}

View file

@ -5,8 +5,7 @@ use chalk_ir::{
visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor},
DebruijnIndex,
};
use hir_def::{visibility::Visibility, AdtId, EnumVariantId, HasModule, ModuleId, VariantId};
use intern::sym;
use hir_def::{visibility::Visibility, AdtId, EnumVariantId, ModuleId, VariantId};
use rustc_hash::FxHashSet;
use crate::{
@ -118,11 +117,6 @@ impl UninhabitedFrom<'_> {
variant: VariantId,
subst: &Substitution,
) -> ControlFlow<VisiblyUninhabited> {
let is_local = variant.krate(self.db.upcast()) == self.target_mod.krate();
if !is_local && self.db.attrs(variant.into()).by_key(&sym::non_exhaustive).exists() {
return CONTINUE_OPAQUELY_INHABITED;
}
let variant_data = self.db.variant_data(variant);
let fields = variant_data.fields();
if fields.is_empty() {

View file

@ -84,12 +84,14 @@ pub use infer::{
cast::CastError,
closure::{CaptureKind, CapturedItem},
could_coerce, could_unify, could_unify_deeply, Adjust, Adjustment, AutoBorrow, BindingMode,
InferenceDiagnostic, InferenceResult, OverloadedDeref, PointerCast,
InferenceDiagnostic, InferenceResult, InferenceTyDiagnosticSource, OverloadedDeref,
PointerCast,
};
pub use interner::Interner;
pub use lower::{
associated_type_shorthand_candidates, ImplTraitLoweringMode, ParamLoweringMode, TyDefId,
TyLoweringContext, ValueTyDefId,
associated_type_shorthand_candidates, GenericArgsProhibitedReason, ImplTraitLoweringMode,
ParamLoweringMode, TyDefId, TyLoweringContext, TyLoweringDiagnostic, TyLoweringDiagnosticKind,
ValueTyDefId,
};
pub use mapping::{
from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, from_placeholder_idx,
@ -385,7 +387,6 @@ pub enum FnAbi {
Fastcall,
FastcallUnwind,
Msp430Interrupt,
PlatformIntrinsic,
PtxKernel,
RiscvInterruptM,
RiscvInterruptS,
@ -444,7 +445,6 @@ impl FnAbi {
s if *s == sym::fastcall_dash_unwind => FnAbi::FastcallUnwind,
s if *s == sym::fastcall => FnAbi::Fastcall,
s if *s == sym::msp430_dash_interrupt => FnAbi::Msp430Interrupt,
s if *s == sym::platform_dash_intrinsic => FnAbi::PlatformIntrinsic,
s if *s == sym::ptx_dash_kernel => FnAbi::PtxKernel,
s if *s == sym::riscv_dash_interrupt_dash_m => FnAbi::RiscvInterruptM,
s if *s == sym::riscv_dash_interrupt_dash_s => FnAbi::RiscvInterruptS,
@ -487,7 +487,6 @@ impl FnAbi {
FnAbi::Fastcall => "fastcall",
FnAbi::FastcallUnwind => "fastcall-unwind",
FnAbi::Msp430Interrupt => "msp430-interrupt",
FnAbi::PlatformIntrinsic => "platform-intrinsic",
FnAbi::PtxKernel => "ptx-kernel",
FnAbi::RiscvInterruptM => "riscv-interrupt-m",
FnAbi::RiscvInterruptS => "riscv-interrupt-s",
@ -646,7 +645,7 @@ pub(crate) fn fold_free_vars<T: HasInterner<Interner = Interner> + TypeFoldable<
F2: FnMut(Ty, BoundVar, DebruijnIndex) -> Const,
> TypeFolder<Interner> for FreeVarFolder<F1, F2>
{
fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner, Error = Self::Error> {
fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner> {
self
}
@ -697,7 +696,7 @@ pub(crate) fn fold_tys_and_consts<T: HasInterner<Interner = Interner> + TypeFold
impl<F: FnMut(Either<Ty, Const>, DebruijnIndex) -> Either<Ty, Const>> TypeFolder<Interner>
for TyFolder<F>
{
fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner, Error = Self::Error> {
fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner> {
self
}

View file

@ -33,8 +33,8 @@ use hir_def::{
path::{GenericArg, GenericArgs, ModPath, Path, PathKind, PathSegment, PathSegments},
resolver::{HasResolver, LifetimeNs, Resolver, TypeNs},
type_ref::{
ConstRef, LifetimeRef, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, TypeRef,
TypeRefId, TypesMap, TypesSourceMap,
ConstRef, LifetimeRef, PathId, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound,
TypeRef, TypeRefId, TypesMap, TypesSourceMap,
},
AdtId, AssocItemId, CallableDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId,
FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, InTypeConstLoc, ItemContainerId,
@ -48,7 +48,7 @@ use rustc_pattern_analysis::Captures;
use smallvec::SmallVec;
use stdx::{impl_from, never};
use syntax::ast;
use triomphe::Arc;
use triomphe::{Arc, ThinArc};
use crate::{
all_super_traits,
@ -102,6 +102,31 @@ impl ImplTraitLoweringState {
}
}
type TypeSource = Either<TypeRefId, hir_def::type_ref::TypeSource>;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct TyLoweringDiagnostic {
pub source: TypeSource,
pub kind: TyLoweringDiagnosticKind,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum TyLoweringDiagnosticKind {
GenericArgsProhibited { segment: u32, reason: GenericArgsProhibitedReason },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GenericArgsProhibitedReason {
Module,
TyParam,
SelfTy,
PrimitiveTy,
/// When there is a generic enum, within the expression `Enum::Variant`,
/// either `Enum` or `Variant` are allowed to have generic arguments, but not both.
// FIXME: This is not used now but it should be.
EnumVariant,
}
#[derive(Debug)]
pub struct TyLoweringContext<'a> {
pub db: &'a dyn HirDatabase,
@ -125,6 +150,7 @@ pub struct TyLoweringContext<'a> {
expander: Option<Expander>,
/// Tracks types with explicit `?Sized` bounds.
pub(crate) unsized_types: FxHashSet<Ty>,
pub(crate) diagnostics: Vec<TyLoweringDiagnostic>,
}
impl<'a> TyLoweringContext<'a> {
@ -159,6 +185,7 @@ impl<'a> TyLoweringContext<'a> {
type_param_mode,
expander: None,
unsized_types: FxHashSet::default(),
diagnostics: Vec::new(),
}
}
@ -198,6 +225,20 @@ impl<'a> TyLoweringContext<'a> {
self.type_param_mode = type_param_mode;
self
}
pub fn push_diagnostic(&mut self, type_ref: TypeRefId, kind: TyLoweringDiagnosticKind) {
let source = match self.types_source_map {
Some(source_map) => {
let Ok(source) = source_map.type_syntax(type_ref) else {
stdx::never!("error in synthetic type");
return;
};
Either::Right(source)
}
None => Either::Left(type_ref),
};
self.diagnostics.push(TyLoweringDiagnostic { source, kind });
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
@ -264,7 +305,8 @@ impl<'a> TyLoweringContext<'a> {
.intern(Interner)
}
TypeRef::Path(path) => {
let (ty, res_) = self.lower_path(path);
let (ty, res_) =
self.lower_path(path, PathId::from_type_ref_unchecked(type_ref_id));
res = res_;
ty
}
@ -463,6 +505,7 @@ impl<'a> TyLoweringContext<'a> {
impl_trait_mode: mem::take(&mut self.impl_trait_mode),
expander: self.expander.take(),
unsized_types: mem::take(&mut self.unsized_types),
diagnostics: mem::take(&mut self.diagnostics),
};
let ty = inner_ctx.lower_ty(type_ref);
@ -470,6 +513,7 @@ impl<'a> TyLoweringContext<'a> {
self.impl_trait_mode = inner_ctx.impl_trait_mode;
self.expander = inner_ctx.expander;
self.unsized_types = inner_ctx.unsized_types;
self.diagnostics = inner_ctx.diagnostics;
self.expander.as_mut().unwrap().exit(mark);
Some(ty)
@ -541,6 +585,10 @@ impl<'a> TyLoweringContext<'a> {
resolved_segment: PathSegment<'_>,
remaining_segments: PathSegments<'_>,
infer_args: bool,
on_prohibited_generics_for_resolved_segment: &mut dyn FnMut(
&mut Self,
GenericArgsProhibitedReason,
),
) -> (Ty, Option<TypeNs>) {
let ty = match resolution {
TypeNs::TraitId(trait_) => {
@ -607,28 +655,44 @@ impl<'a> TyLoweringContext<'a> {
// FIXME(trait_alias): Implement trait alias.
return (TyKind::Error.intern(Interner), None);
}
TypeNs::GenericParam(param_id) => match self.type_param_mode {
ParamLoweringMode::Placeholder => {
TyKind::Placeholder(to_placeholder_idx(self.db, param_id.into()))
TypeNs::GenericParam(param_id) => {
if resolved_segment.args_and_bindings.is_some() {
on_prohibited_generics_for_resolved_segment(
self,
GenericArgsProhibitedReason::TyParam,
);
}
ParamLoweringMode::Variable => {
let idx = match self
.generics()
.expect("generics in scope")
.type_or_const_param_idx(param_id.into())
{
None => {
never!("no matching generics");
return (TyKind::Error.intern(Interner), None);
}
Some(idx) => idx,
};
TyKind::BoundVar(BoundVar::new(self.in_binders, idx))
match self.type_param_mode {
ParamLoweringMode::Placeholder => {
TyKind::Placeholder(to_placeholder_idx(self.db, param_id.into()))
}
ParamLoweringMode::Variable => {
let idx = match self
.generics()
.expect("generics in scope")
.type_or_const_param_idx(param_id.into())
{
None => {
never!("no matching generics");
return (TyKind::Error.intern(Interner), None);
}
Some(idx) => idx,
};
TyKind::BoundVar(BoundVar::new(self.in_binders, idx))
}
}
.intern(Interner)
}
.intern(Interner),
TypeNs::SelfType(impl_id) => {
if resolved_segment.args_and_bindings.is_some() {
on_prohibited_generics_for_resolved_segment(
self,
GenericArgsProhibitedReason::SelfTy,
);
}
let generics = self.generics().expect("impl should have generic param scope");
match self.type_param_mode {
@ -654,6 +718,13 @@ impl<'a> TyLoweringContext<'a> {
}
}
TypeNs::AdtSelfType(adt) => {
if resolved_segment.args_and_bindings.is_some() {
on_prohibited_generics_for_resolved_segment(
self,
GenericArgsProhibitedReason::SelfTy,
);
}
let generics = generics(self.db.upcast(), adt.into());
let substs = match self.type_param_mode {
ParamLoweringMode::Placeholder => generics.placeholder_subst(self.db),
@ -666,6 +737,12 @@ impl<'a> TyLoweringContext<'a> {
TypeNs::AdtId(it) => self.lower_path_inner(resolved_segment, it.into(), infer_args),
TypeNs::BuiltinType(it) => {
if resolved_segment.args_and_bindings.is_some() {
on_prohibited_generics_for_resolved_segment(
self,
GenericArgsProhibitedReason::PrimitiveTy,
);
}
self.lower_path_inner(resolved_segment, it.into(), infer_args)
}
TypeNs::TypeAliasId(it) => {
@ -677,7 +754,7 @@ impl<'a> TyLoweringContext<'a> {
self.lower_ty_relative_path(ty, Some(resolution), remaining_segments)
}
pub(crate) fn lower_path(&mut self, path: &Path) -> (Ty, Option<TypeNs>) {
pub(crate) fn lower_path(&mut self, path: &Path, path_id: PathId) -> (Ty, Option<TypeNs>) {
// Resolve the path (in type namespace)
if let Some(type_ref) = path.type_anchor() {
let (ty, res) = self.lower_ty_ext(type_ref);
@ -692,19 +769,44 @@ impl<'a> TyLoweringContext<'a> {
if matches!(resolution, TypeNs::TraitId(_)) && remaining_index.is_none() {
// trait object type without dyn
let bound = TypeBound::Path(path.clone(), TraitBoundModifier::None);
let bound = TypeBound::Path(path_id, TraitBoundModifier::None);
let ty = self.lower_dyn_trait(&[bound]);
return (ty, None);
}
let (resolved_segment, remaining_segments) = match remaining_index {
None => (
path.segments().last().expect("resolved path has at least one element"),
PathSegments::EMPTY,
),
Some(i) => (path.segments().get(i - 1).unwrap(), path.segments().skip(i)),
};
self.lower_partly_resolved_path(resolution, resolved_segment, remaining_segments, false)
let (module_segments, resolved_segment_idx, resolved_segment, remaining_segments) =
match remaining_index {
None => (
path.segments().strip_last(),
path.segments().len() - 1,
path.segments().last().expect("resolved path has at least one element"),
PathSegments::EMPTY,
),
Some(i) => (
path.segments().take(i - 1),
i - 1,
path.segments().get(i - 1).unwrap(),
path.segments().skip(i),
),
};
self.prohibit_generics(path_id, 0, module_segments, GenericArgsProhibitedReason::Module);
self.lower_partly_resolved_path(
resolution,
resolved_segment,
remaining_segments,
false,
&mut |this, reason| {
this.push_diagnostic(
path_id.type_ref(),
TyLoweringDiagnosticKind::GenericArgsProhibited {
segment: resolved_segment_idx as u32,
reason,
},
)
},
)
}
fn select_associated_type(&mut self, res: Option<TypeNs>, segment: PathSegment<'_>) -> Ty {
@ -741,12 +843,8 @@ impl<'a> TyLoweringContext<'a> {
// generic params. It's inefficient to splice the `Substitution`s, so we may want
// that method to optionally take parent `Substitution` as we already know them at
// this point (`t.substitution`).
let substs = self.substs_from_path_segment(
segment.clone(),
Some(associated_ty.into()),
false,
None,
);
let substs =
self.substs_from_path_segment(segment, Some(associated_ty.into()), false, None);
let len_self =
crate::generics::generics(self.db.upcast(), associated_ty.into()).len_self();
@ -998,12 +1096,41 @@ impl<'a> TyLoweringContext<'a> {
TraitRef { trait_id: to_chalk_trait_id(resolved), substitution: substs }
}
fn lower_trait_ref_from_path(&mut self, path: &Path, explicit_self_ty: Ty) -> Option<TraitRef> {
fn prohibit_generics(
&mut self,
path_id: PathId,
idx: u32,
segments: PathSegments<'_>,
reason: GenericArgsProhibitedReason,
) {
segments.iter().zip(idx..).for_each(|(segment, idx)| {
if segment.args_and_bindings.is_some() {
self.push_diagnostic(
path_id.type_ref(),
TyLoweringDiagnosticKind::GenericArgsProhibited { segment: idx, reason },
);
}
});
}
fn lower_trait_ref_from_path(
&mut self,
path_id: PathId,
explicit_self_ty: Ty,
) -> Option<TraitRef> {
let path = &self.types_map[path_id];
let resolved = match self.resolver.resolve_path_in_type_ns_fully(self.db.upcast(), path)? {
// FIXME(trait_alias): We need to handle trait alias here.
TypeNs::TraitId(tr) => tr,
_ => return None,
};
// Do this after we verify it's indeed a trait to not confuse the user if they're not modules.
self.prohibit_generics(
path_id,
0,
path.segments().strip_last(),
GenericArgsProhibitedReason::Module,
);
let segment = path.segments().last().expect("path should have at least one segment");
Some(self.lower_trait_ref_from_resolved_path(resolved, segment, explicit_self_ty))
}
@ -1013,7 +1140,7 @@ impl<'a> TyLoweringContext<'a> {
trait_ref: &HirTraitRef,
explicit_self_ty: Ty,
) -> Option<TraitRef> {
self.lower_trait_ref_from_path(&trait_ref.path, explicit_self_ty)
self.lower_trait_ref_from_path(trait_ref.path, explicit_self_ty)
}
fn trait_ref_substs_from_path(
@ -1072,11 +1199,11 @@ impl<'a> TyLoweringContext<'a> {
) -> impl Iterator<Item = QuantifiedWhereClause> + use<'b, 'a> {
let mut trait_ref = None;
let clause = match bound {
TypeBound::Path(path, TraitBoundModifier::None) => {
&TypeBound::Path(path, TraitBoundModifier::None) => {
trait_ref = self.lower_trait_ref_from_path(path, self_ty);
trait_ref.clone().map(WhereClause::Implemented).map(crate::wrap_empty_binders)
}
TypeBound::Path(path, TraitBoundModifier::Maybe) => {
&TypeBound::Path(path, TraitBoundModifier::Maybe) => {
let sized_trait = self
.db
.lang_item(self.resolver.krate(), LangItem::Sized)
@ -1092,7 +1219,7 @@ impl<'a> TyLoweringContext<'a> {
}
None
}
TypeBound::ForLifetime(_, path) => {
&TypeBound::ForLifetime(_, path) => {
// FIXME Don't silently drop the hrtb lifetimes here
trait_ref = self.lower_trait_ref_from_path(path, self_ty);
trait_ref.clone().map(WhereClause::Implemented).map(crate::wrap_empty_binders)
@ -1121,8 +1248,8 @@ impl<'a> TyLoweringContext<'a> {
trait_ref: TraitRef,
) -> impl Iterator<Item = QuantifiedWhereClause> + use<'b, 'a> {
let last_segment = match bound {
TypeBound::Path(path, TraitBoundModifier::None) | TypeBound::ForLifetime(_, path) => {
path.segments().last()
&TypeBound::Path(path, TraitBoundModifier::None) | &TypeBound::ForLifetime(_, path) => {
self.types_map[path].segments().last()
}
TypeBound::Path(_, TraitBoundModifier::Maybe)
| TypeBound::Use(_)
@ -1227,7 +1354,9 @@ impl<'a> TyLoweringContext<'a> {
}
_ => unreachable!(),
}
ext.lower_ty(type_ref)
let ty = ext.lower_ty(type_ref);
self.diagnostics.extend(ext.diagnostics);
ty
} else {
self.lower_ty(type_ref)
};
@ -1523,11 +1652,24 @@ fn named_associated_type_shorthand_candidates<R>(
}
}
/// Build the type of all specific fields of a struct or enum variant.
pub(crate) type Diagnostics = Option<ThinArc<(), TyLoweringDiagnostic>>;
fn create_diagnostics(diagnostics: Vec<TyLoweringDiagnostic>) -> Diagnostics {
(!diagnostics.is_empty()).then(|| ThinArc::from_header_and_iter((), diagnostics.into_iter()))
}
pub(crate) fn field_types_query(
db: &dyn HirDatabase,
variant_id: VariantId,
) -> Arc<ArenaMap<LocalFieldId, Binders<Ty>>> {
db.field_types_with_diagnostics(variant_id).0
}
/// Build the type of all specific fields of a struct or enum variant.
pub(crate) fn field_types_with_diagnostics_query(
db: &dyn HirDatabase,
variant_id: VariantId,
) -> (Arc<ArenaMap<LocalFieldId, Binders<Ty>>>, Diagnostics) {
let var_data = variant_id.variant_data(db.upcast());
let (resolver, def): (_, GenericDefId) = match variant_id {
VariantId::StructId(it) => (it.resolver(db.upcast()), it.into()),
@ -1543,7 +1685,7 @@ pub(crate) fn field_types_query(
for (field_id, field_data) in var_data.fields().iter() {
res.insert(field_id, make_binders(db, &generics, ctx.lower_ty(field_data.type_ref)));
}
Arc::new(res)
(Arc::new(res), create_diagnostics(ctx.diagnostics))
}
/// This query exists only to be used when resolving short-hand associated types
@ -1593,9 +1735,10 @@ pub(crate) fn generic_predicates_for_param_query(
}
match bound {
TypeBound::ForLifetime(_, path) | TypeBound::Path(path, _) => {
&TypeBound::ForLifetime(_, path) | &TypeBound::Path(path, _) => {
// Only lower the bound if the trait could possibly define the associated
// type we're looking for.
let path = &ctx.types_map[path];
let Some(assoc_name) = &assoc_name else { return true };
let Some(TypeNs::TraitId(tr)) =
@ -1743,15 +1886,22 @@ pub(crate) fn generic_predicates_query(
db: &dyn HirDatabase,
def: GenericDefId,
) -> GenericPredicates {
generic_predicates_filtered_by(db, def, |_, _| true)
generic_predicates_filtered_by(db, def, |_, _| true).0
}
/// Resolve the where clause(s) of an item with generics,
/// except the ones inherited from the parent
pub(crate) fn generic_predicates_without_parent_query(
db: &dyn HirDatabase,
def: GenericDefId,
) -> GenericPredicates {
db.generic_predicates_without_parent_with_diagnostics(def).0
}
/// Resolve the where clause(s) of an item with generics,
/// except the ones inherited from the parent
pub(crate) fn generic_predicates_without_parent_with_diagnostics_query(
db: &dyn HirDatabase,
def: GenericDefId,
) -> (GenericPredicates, Diagnostics) {
generic_predicates_filtered_by(db, def, |_, d| *d == def)
}
@ -1761,7 +1911,7 @@ fn generic_predicates_filtered_by<F>(
db: &dyn HirDatabase,
def: GenericDefId,
filter: F,
) -> GenericPredicates
) -> (GenericPredicates, Diagnostics)
where
F: Fn(&WherePredicate, &GenericDefId) -> bool,
{
@ -1802,7 +1952,10 @@ where
);
};
}
GenericPredicates(predicates.is_empty().not().then(|| predicates.into()))
(
GenericPredicates(predicates.is_empty().not().then(|| predicates.into())),
create_diagnostics(ctx.diagnostics),
)
}
/// Generate implicit `: Sized` predicates for all generics that has no `?Sized` bound.
@ -1855,75 +2008,110 @@ impl ops::Deref for GenericDefaults {
}
}
/// Resolve the default type params from generics
pub(crate) fn generic_defaults_query(db: &dyn HirDatabase, def: GenericDefId) -> GenericDefaults {
db.generic_defaults_with_diagnostics(def).0
}
/// Resolve the default type params from generics.
///
/// Diagnostics are only returned for this `GenericDefId` (returned defaults include parents).
pub(crate) fn generic_defaults_with_diagnostics_query(
db: &dyn HirDatabase,
def: GenericDefId,
) -> (GenericDefaults, Diagnostics) {
let generic_params = generics(db.upcast(), def);
if generic_params.len() == 0 {
return GenericDefaults(None);
return (GenericDefaults(None), None);
}
let resolver = def.resolver(db.upcast());
let parent_start_idx = generic_params.len_self();
let mut ctx = TyLoweringContext::new(db, &resolver, TypesMap::EMPTY, def.into())
.with_impl_trait_mode(ImplTraitLoweringMode::Disallowed)
.with_type_param_mode(ParamLoweringMode::Variable);
GenericDefaults(Some(Arc::from_iter(generic_params.iter_with_types_map().enumerate().map(
|(idx, ((id, p), types_map))| {
ctx.types_map = types_map;
match p {
GenericParamDataRef::TypeParamData(p) => {
let ty = p.default.as_ref().map_or(TyKind::Error.intern(Interner), |ty| {
// Each default can only refer to previous parameters.
// Type variable default referring to parameter coming
// after it is forbidden (FIXME: report diagnostic)
fallback_bound_vars(ctx.lower_ty(*ty), idx, parent_start_idx)
});
crate::make_binders(db, &generic_params, ty.cast(Interner))
}
GenericParamDataRef::ConstParamData(p) => {
let GenericParamId::ConstParamId(id) = id else {
unreachable!("Unexpected lifetime or type argument")
};
let mut ctx =
TyLoweringContext::new(db, &resolver, generic_params.self_types_map(), def.into())
.with_impl_trait_mode(ImplTraitLoweringMode::Disallowed)
.with_type_param_mode(ParamLoweringMode::Variable);
let mut idx = 0;
let mut defaults = generic_params
.iter_self()
.map(|(id, p)| {
let result =
handle_generic_param(&mut ctx, idx, id, p, parent_start_idx, &generic_params);
idx += 1;
result
})
.collect::<Vec<_>>();
let diagnostics = create_diagnostics(mem::take(&mut ctx.diagnostics));
defaults.extend(generic_params.iter_parents_with_types_map().map(|((id, p), types_map)| {
ctx.types_map = types_map;
let result = handle_generic_param(&mut ctx, idx, id, p, parent_start_idx, &generic_params);
idx += 1;
result
}));
let defaults = GenericDefaults(Some(Arc::from_iter(defaults)));
return (defaults, diagnostics);
let mut val = p.default.as_ref().map_or_else(
|| unknown_const_as_generic(db.const_param_ty(id)),
|c| {
let param_ty = ctx.lower_ty(p.ty);
let c = ctx.lower_const(c, param_ty);
c.cast(Interner)
},
);
// Each default can only refer to previous parameters, see above.
val = fallback_bound_vars(val, idx, parent_start_idx);
make_binders(db, &generic_params, val)
}
GenericParamDataRef::LifetimeParamData(_) => {
make_binders(db, &generic_params, error_lifetime().cast(Interner))
}
fn handle_generic_param(
ctx: &mut TyLoweringContext<'_>,
idx: usize,
id: GenericParamId,
p: GenericParamDataRef<'_>,
parent_start_idx: usize,
generic_params: &Generics,
) -> Binders<crate::GenericArg> {
match p {
GenericParamDataRef::TypeParamData(p) => {
let ty = p.default.as_ref().map_or(TyKind::Error.intern(Interner), |ty| {
// Each default can only refer to previous parameters.
// Type variable default referring to parameter coming
// after it is forbidden (FIXME: report diagnostic)
fallback_bound_vars(ctx.lower_ty(*ty), idx, parent_start_idx)
});
crate::make_binders(ctx.db, generic_params, ty.cast(Interner))
}
},
))))
GenericParamDataRef::ConstParamData(p) => {
let GenericParamId::ConstParamId(id) = id else {
unreachable!("Unexpected lifetime or type argument")
};
let mut val = p.default.as_ref().map_or_else(
|| unknown_const_as_generic(ctx.db.const_param_ty(id)),
|c| {
let param_ty = ctx.lower_ty(p.ty);
let c = ctx.lower_const(c, param_ty);
c.cast(Interner)
},
);
// Each default can only refer to previous parameters, see above.
val = fallback_bound_vars(val, idx, parent_start_idx);
make_binders(ctx.db, generic_params, val)
}
GenericParamDataRef::LifetimeParamData(_) => {
make_binders(ctx.db, generic_params, error_lifetime().cast(Interner))
}
}
}
}
pub(crate) fn generic_defaults_recover(
pub(crate) fn generic_defaults_with_diagnostics_recover(
db: &dyn HirDatabase,
_cycle: &Cycle,
def: &GenericDefId,
) -> GenericDefaults {
) -> (GenericDefaults, Diagnostics) {
let generic_params = generics(db.upcast(), *def);
if generic_params.len() == 0 {
return GenericDefaults(None);
return (GenericDefaults(None), None);
}
// FIXME: this code is not covered in tests.
// we still need one default per parameter
GenericDefaults(Some(Arc::from_iter(generic_params.iter_id().map(|id| {
let defaults = GenericDefaults(Some(Arc::from_iter(generic_params.iter_id().map(|id| {
let val = match id {
GenericParamId::TypeParamId(_) => TyKind::Error.intern(Interner).cast(Interner),
GenericParamId::ConstParamId(id) => unknown_const_as_generic(db.const_param_ty(id)),
GenericParamId::LifetimeParamId(_) => error_lifetime().cast(Interner),
};
crate::make_binders(db, &generic_params, val)
}))))
}))));
(defaults, None)
}
fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig {
@ -2066,7 +2254,10 @@ fn type_for_adt(db: &dyn HirDatabase, adt: AdtId) -> Binders<Ty> {
make_binders(db, &generics, ty)
}
fn type_for_type_alias(db: &dyn HirDatabase, t: TypeAliasId) -> Binders<Ty> {
pub(crate) fn type_for_type_alias_with_diagnostics_query(
db: &dyn HirDatabase,
t: TypeAliasId,
) -> (Binders<Ty>, Diagnostics) {
let generics = generics(db.upcast(), t.into());
let resolver = t.resolver(db.upcast());
let type_alias_data = db.type_alias_data(t);
@ -2081,7 +2272,7 @@ fn type_for_type_alias(db: &dyn HirDatabase, t: TypeAliasId) -> Binders<Ty> {
.map(|type_ref| ctx.lower_ty(type_ref))
.unwrap_or_else(|| TyKind::Error.intern(Interner))
};
make_binders(db, &generics, inner)
(make_binders(db, &generics, inner), create_diagnostics(ctx.diagnostics))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -2124,7 +2315,7 @@ pub(crate) fn ty_query(db: &dyn HirDatabase, def: TyDefId) -> Binders<Ty> {
match def {
TyDefId::BuiltinType(it) => Binders::empty(Interner, TyBuilder::builtin(it)),
TyDefId::AdtId(it) => type_for_adt(db, it),
TyDefId::TypeAliasId(it) => type_for_type_alias(db, it),
TyDefId::TypeAliasId(it) => db.type_for_type_alias_with_diagnostics(it).0,
}
}
@ -2149,47 +2340,73 @@ pub(crate) fn value_ty_query(db: &dyn HirDatabase, def: ValueTyDefId) -> Option<
}
pub(crate) fn impl_self_ty_query(db: &dyn HirDatabase, impl_id: ImplId) -> Binders<Ty> {
db.impl_self_ty_with_diagnostics(impl_id).0
}
pub(crate) fn impl_self_ty_with_diagnostics_query(
db: &dyn HirDatabase,
impl_id: ImplId,
) -> (Binders<Ty>, Diagnostics) {
let impl_data = db.impl_data(impl_id);
let resolver = impl_id.resolver(db.upcast());
let generics = generics(db.upcast(), impl_id.into());
let mut ctx = TyLoweringContext::new(db, &resolver, &impl_data.types_map, impl_id.into())
.with_type_param_mode(ParamLoweringMode::Variable);
make_binders(db, &generics, ctx.lower_ty(impl_data.self_ty))
(
make_binders(db, &generics, ctx.lower_ty(impl_data.self_ty)),
create_diagnostics(ctx.diagnostics),
)
}
pub(crate) fn const_param_ty_query(db: &dyn HirDatabase, def: ConstParamId) -> Ty {
db.const_param_ty_with_diagnostics(def).0
}
// returns None if def is a type arg
pub(crate) fn const_param_ty_query(db: &dyn HirDatabase, def: ConstParamId) -> Ty {
pub(crate) fn const_param_ty_with_diagnostics_query(
db: &dyn HirDatabase,
def: ConstParamId,
) -> (Ty, Diagnostics) {
let parent_data = db.generic_params(def.parent());
let data = &parent_data[def.local_id()];
let resolver = def.parent().resolver(db.upcast());
let mut ctx =
TyLoweringContext::new(db, &resolver, &parent_data.types_map, def.parent().into());
match data {
let ty = match data {
TypeOrConstParamData::TypeParamData(_) => {
never!();
Ty::new(Interner, TyKind::Error)
}
TypeOrConstParamData::ConstParamData(d) => ctx.lower_ty(d.ty),
}
};
(ty, create_diagnostics(ctx.diagnostics))
}
pub(crate) fn impl_self_ty_recover(
pub(crate) fn impl_self_ty_with_diagnostics_recover(
db: &dyn HirDatabase,
_cycle: &Cycle,
impl_id: &ImplId,
) -> Binders<Ty> {
) -> (Binders<Ty>, Diagnostics) {
let generics = generics(db.upcast(), (*impl_id).into());
make_binders(db, &generics, TyKind::Error.intern(Interner))
(make_binders(db, &generics, TyKind::Error.intern(Interner)), None)
}
pub(crate) fn impl_trait_query(db: &dyn HirDatabase, impl_id: ImplId) -> Option<Binders<TraitRef>> {
db.impl_trait_with_diagnostics(impl_id).map(|it| it.0)
}
pub(crate) fn impl_trait_with_diagnostics_query(
db: &dyn HirDatabase,
impl_id: ImplId,
) -> Option<(Binders<TraitRef>, Diagnostics)> {
let impl_data = db.impl_data(impl_id);
let resolver = impl_id.resolver(db.upcast());
let mut ctx = TyLoweringContext::new(db, &resolver, &impl_data.types_map, impl_id.into())
.with_type_param_mode(ParamLoweringMode::Variable);
let (self_ty, binders) = db.impl_self_ty(impl_id).into_value_and_skipped_binders();
let target_trait = impl_data.target_trait.as_ref()?;
Some(Binders::new(binders, ctx.lower_trait_ref(target_trait, self_ty)?))
let trait_ref = Binders::new(binders, ctx.lower_trait_ref(target_trait, self_ty)?);
Some((trait_ref, create_diagnostics(ctx.diagnostics)))
}
pub(crate) fn return_type_impl_traits(

View file

@ -2023,11 +2023,11 @@ pub fn mir_body_for_closure_query(
ctx.result.locals.alloc(Local { ty: infer[*root].clone() });
let closure_local = ctx.result.locals.alloc(Local {
ty: match kind {
FnTrait::FnOnce => infer[expr].clone(),
FnTrait::FnMut => {
FnTrait::FnOnce | FnTrait::AsyncFnOnce => infer[expr].clone(),
FnTrait::FnMut | FnTrait::AsyncFnMut => {
TyKind::Ref(Mutability::Mut, error_lifetime(), infer[expr].clone()).intern(Interner)
}
FnTrait::Fn => {
FnTrait::Fn | FnTrait::AsyncFn => {
TyKind::Ref(Mutability::Not, error_lifetime(), infer[expr].clone()).intern(Interner)
}
},
@ -2055,8 +2055,10 @@ pub fn mir_body_for_closure_query(
let mut err = None;
let closure_local = ctx.result.locals.iter().nth(1).unwrap().0;
let closure_projection = match kind {
FnTrait::FnOnce => vec![],
FnTrait::FnMut | FnTrait::Fn => vec![ProjectionElem::Deref],
FnTrait::FnOnce | FnTrait::AsyncFnOnce => vec![],
FnTrait::FnMut | FnTrait::Fn | FnTrait::AsyncFnMut | FnTrait::AsyncFn => {
vec![ProjectionElem::Deref]
}
};
ctx.result.walk_places(|p, store| {
if let Some(it) = upvar_map.get(&p.local) {

View file

@ -18,13 +18,13 @@ use std::sync::LazyLock;
use base_db::SourceDatabaseFileInputExt as _;
use expect_test::Expect;
use hir_def::{
body::{Body, BodySourceMap, SyntheticSyntax},
body::{Body, BodySourceMap},
db::DefDatabase,
hir::{ExprId, Pat, PatId},
item_scope::ItemScope,
nameres::DefMap,
src::HasSource,
AssocItemId, DefWithBodyId, HasModule, LocalModuleId, Lookup, ModuleDefId,
AssocItemId, DefWithBodyId, HasModule, LocalModuleId, Lookup, ModuleDefId, SyntheticSyntax,
};
use hir_expand::{db::ExpandDatabase, FileRange, InFile};
use itertools::Itertools;

View file

@ -942,3 +942,19 @@ fn main() {
"#,
)
}
#[test]
fn regression_18626() {
check_no_mismatches(
r#"
fn f() {
trait T {
fn f() {}
}
impl T for i32 {}
impl T for u32 {}
&[i32::f, u32::f] as &[fn()];
}
"#,
);
}

View file

@ -1630,6 +1630,29 @@ fn test<'lifetime>(
);
}
#[test]
fn lifetime_bounds() {
check_infer(
r#"
//- minicore: sized, coerce_unsized
trait Trait<'a>: Sized {
fn f(&'a self) {}
}
fn test<'a, 'b: 'a>(it: impl Trait<'a>){
it.f();
}
"#,
expect![[r#"
38..42 'self': &'a Self
44..46 '{}': ()
69..71 'it': impl Trait<'a>
88..103 '{ it.f(); }': ()
94..96 'it': impl Trait<'a>
94..100 'it.f()': ()
"#]],
);
}
#[test]
fn error_bound_chalk() {
check_types(
@ -4811,3 +4834,53 @@ fn bar(v: *const ()) {
"#]],
);
}
#[test]
fn async_fn_traits() {
check_infer(
r#"
//- minicore: async_fn
async fn foo<T: AsyncFn(u32) -> i32>(a: T) {
let fut1 = a(0);
fut1.await;
}
async fn bar<T: AsyncFnMut(u32) -> i32>(mut b: T) {
let fut2 = b(0);
fut2.await;
}
async fn baz<T: AsyncFnOnce(u32) -> i32>(c: T) {
let fut3 = c(0);
fut3.await;
}
"#,
expect![[r#"
37..38 'a': T
43..83 '{ ...ait; }': ()
43..83 '{ ...ait; }': impl Future<Output = ()>
53..57 'fut1': AsyncFnMut::CallRefFuture<'?, T, (u32,)>
60..61 'a': T
60..64 'a(0)': AsyncFnMut::CallRefFuture<'?, T, (u32,)>
62..63 '0': u32
70..74 'fut1': AsyncFnMut::CallRefFuture<'?, T, (u32,)>
70..80 'fut1.await': i32
124..129 'mut b': T
134..174 '{ ...ait; }': ()
134..174 '{ ...ait; }': impl Future<Output = ()>
144..148 'fut2': AsyncFnMut::CallRefFuture<'?, T, (u32,)>
151..152 'b': T
151..155 'b(0)': AsyncFnMut::CallRefFuture<'?, T, (u32,)>
153..154 '0': u32
161..165 'fut2': AsyncFnMut::CallRefFuture<'?, T, (u32,)>
161..171 'fut2.await': i32
216..217 'c': T
222..262 '{ ...ait; }': ()
222..262 '{ ...ait; }': impl Future<Output = ()>
232..236 'fut3': AsyncFnOnce::CallOnceFuture<T, (u32,)>
239..240 'c': T
239..243 'c(0)': AsyncFnOnce::CallOnceFuture<T, (u32,)>
241..242 '0': u32
249..253 'fut3': AsyncFnOnce::CallOnceFuture<T, (u32,)>
249..259 'fut3.await': i32
"#]],
);
}

View file

@ -220,6 +220,10 @@ pub enum FnTrait {
FnOnce,
FnMut,
Fn,
AsyncFnOnce,
AsyncFnMut,
AsyncFn,
}
impl fmt::Display for FnTrait {
@ -228,6 +232,9 @@ impl fmt::Display for FnTrait {
FnTrait::FnOnce => write!(f, "FnOnce"),
FnTrait::FnMut => write!(f, "FnMut"),
FnTrait::Fn => write!(f, "Fn"),
FnTrait::AsyncFnOnce => write!(f, "AsyncFnOnce"),
FnTrait::AsyncFnMut => write!(f, "AsyncFnMut"),
FnTrait::AsyncFn => write!(f, "AsyncFn"),
}
}
}
@ -238,6 +245,9 @@ impl FnTrait {
FnTrait::FnOnce => "call_once",
FnTrait::FnMut => "call_mut",
FnTrait::Fn => "call",
FnTrait::AsyncFnOnce => "async_call_once",
FnTrait::AsyncFnMut => "async_call_mut",
FnTrait::AsyncFn => "async_call",
}
}
@ -246,6 +256,9 @@ impl FnTrait {
FnTrait::FnOnce => LangItem::FnOnce,
FnTrait::FnMut => LangItem::FnMut,
FnTrait::Fn => LangItem::Fn,
FnTrait::AsyncFnOnce => LangItem::AsyncFnOnce,
FnTrait::AsyncFnMut => LangItem::AsyncFnMut,
FnTrait::AsyncFn => LangItem::AsyncFn,
}
}
@ -254,15 +267,19 @@ impl FnTrait {
LangItem::FnOnce => Some(FnTrait::FnOnce),
LangItem::FnMut => Some(FnTrait::FnMut),
LangItem::Fn => Some(FnTrait::Fn),
LangItem::AsyncFnOnce => Some(FnTrait::AsyncFnOnce),
LangItem::AsyncFnMut => Some(FnTrait::AsyncFnMut),
LangItem::AsyncFn => Some(FnTrait::AsyncFn),
_ => None,
}
}
pub const fn to_chalk_ir(self) -> rust_ir::ClosureKind {
// Chalk doesn't support async fn traits.
match self {
FnTrait::FnOnce => rust_ir::ClosureKind::FnOnce,
FnTrait::FnMut => rust_ir::ClosureKind::FnMut,
FnTrait::Fn => rust_ir::ClosureKind::Fn,
FnTrait::AsyncFnOnce | FnTrait::FnOnce => rust_ir::ClosureKind::FnOnce,
FnTrait::AsyncFnMut | FnTrait::FnMut => rust_ir::ClosureKind::FnMut,
FnTrait::AsyncFn | FnTrait::Fn => rust_ir::ClosureKind::Fn,
}
}
@ -271,6 +288,9 @@ impl FnTrait {
FnTrait::FnOnce => Name::new_symbol_root(sym::call_once.clone()),
FnTrait::FnMut => Name::new_symbol_root(sym::call_mut.clone()),
FnTrait::Fn => Name::new_symbol_root(sym::call.clone()),
FnTrait::AsyncFnOnce => Name::new_symbol_root(sym::async_call_once.clone()),
FnTrait::AsyncFnMut => Name::new_symbol_root(sym::async_call_mut.clone()),
FnTrait::AsyncFn => Name::new_symbol_root(sym::async_call.clone()),
}
}

View file

@ -185,7 +185,7 @@ fn direct_super_traits_cb(db: &dyn DefDatabase, trait_: TraitId, cb: impl FnMut(
}
};
match is_trait {
true => bound.as_path(),
true => bound.as_path(&generic_params.types_map),
false => None,
}
}

View file

@ -33,6 +33,14 @@ syntax.workspace = true
tt.workspace = true
span.workspace = true
[dev-dependencies]
expect-test.workspace = true
# local deps
test-utils.workspace = true
test-fixture.workspace = true
syntax-bridge.workspace = true
[features]
in-rust-tree = ["hir-expand/in-rust-tree"]

View file

@ -3,21 +3,35 @@
//!
//! This probably isn't the best way to do this -- ideally, diagnostics should
//! be expressed in terms of hir types themselves.
pub use hir_ty::diagnostics::{CaseType, IncorrectCase};
use hir_ty::{
db::HirDatabase, diagnostics::BodyValidationDiagnostic, CastError, InferenceDiagnostic,
};
use cfg::{CfgExpr, CfgOptions};
use either::Either;
pub use hir_def::VariantId;
use hir_def::{body::SyntheticSyntax, hir::ExprOrPatId, path::ModPath, AssocItemId, DefWithBodyId};
use hir_def::{
hir::ExprOrPatId,
path::{hir_segment_to_ast_segment, ModPath},
type_ref::TypesSourceMap,
AssocItemId, DefWithBodyId, SyntheticSyntax,
};
use hir_expand::{name::Name, HirFileId, InFile};
use syntax::{ast, AstPtr, SyntaxError, SyntaxNodePtr, TextRange};
use hir_ty::{
db::HirDatabase,
diagnostics::{BodyValidationDiagnostic, UnsafetyReason},
CastError, InferenceDiagnostic, InferenceTyDiagnosticSource, TyLoweringDiagnostic,
TyLoweringDiagnosticKind,
};
use syntax::{
ast::{self, HasGenericArgs},
AstPtr, SyntaxError, SyntaxNodePtr, TextRange,
};
use triomphe::Arc;
use crate::{AssocItem, Field, Local, Trait, Type};
pub use hir_def::VariantId;
pub use hir_ty::{
diagnostics::{CaseType, IncorrectCase},
GenericArgsProhibitedReason,
};
macro_rules! diagnostics {
($($diag:ident,)*) => {
#[derive(Debug)]
@ -96,6 +110,7 @@ diagnostics![
UnresolvedIdent,
UnusedMut,
UnusedVariable,
GenericArgsProhibited,
];
#[derive(Debug)]
@ -258,9 +273,10 @@ pub struct PrivateField {
#[derive(Debug)]
pub struct MissingUnsafe {
pub expr: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
pub node: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
/// If true, the diagnostics is an `unsafe_op_in_unsafe_fn` lint instead of a hard error.
pub only_lint: bool,
pub reason: UnsafetyReason,
}
#[derive(Debug)]
@ -385,6 +401,12 @@ pub struct InvalidCast {
pub cast_ty: Type,
}
#[derive(Debug)]
pub struct GenericArgsProhibited {
pub args: InFile<AstPtr<Either<ast::GenericArgList, ast::ParenthesizedArgList>>>,
pub reason: GenericArgsProhibitedReason,
}
impl AnyDiagnostic {
pub(crate) fn body_validation_diagnostic(
db: &dyn HirDatabase,
@ -524,6 +546,7 @@ impl AnyDiagnostic {
db: &dyn HirDatabase,
def: DefWithBodyId,
d: &InferenceDiagnostic,
outer_types_source_map: &TypesSourceMap,
source_map: &hir_def::body::BodySourceMap,
) -> Option<AnyDiagnostic> {
let expr_syntax = |expr| {
@ -637,6 +660,44 @@ impl AnyDiagnostic {
let cast_ty = Type::new(db, def, cast_ty.clone());
InvalidCast { expr, error: *error, expr_ty, cast_ty }.into()
}
InferenceDiagnostic::TyDiagnostic { source, diag } => {
let source_map = match source {
InferenceTyDiagnosticSource::Body => &source_map.types,
InferenceTyDiagnosticSource::Signature => outer_types_source_map,
};
Self::ty_diagnostic(diag, source_map, db)?
}
})
}
pub(crate) fn ty_diagnostic(
diag: &TyLoweringDiagnostic,
source_map: &TypesSourceMap,
db: &dyn HirDatabase,
) -> Option<AnyDiagnostic> {
let source = match diag.source {
Either::Left(type_ref_id) => {
let Ok(source) = source_map.type_syntax(type_ref_id) else {
stdx::never!("error on synthetic type syntax");
return None;
};
source
}
Either::Right(source) => source,
};
let syntax = || source.value.to_node(&db.parse_or_expand(source.file_id));
Some(match diag.kind {
TyLoweringDiagnosticKind::GenericArgsProhibited { segment, reason } => {
let ast::Type::PathType(syntax) = syntax() else { return None };
let segment = hir_segment_to_ast_segment(&syntax.path()?, segment)?;
let args = if let Some(generics) = segment.generic_arg_list() {
AstPtr::new(&generics).wrap_left()
} else {
AstPtr::new(&segment.parenthesized_arg_list()?).wrap_right()
};
let args = source.with_value(args);
GenericArgsProhibited { args, reason }.into()
}
})
}
}

View file

@ -132,12 +132,18 @@ impl HirDisplay for Function {
} else {
match &data.types_map[data.ret_type] {
TypeRef::ImplTrait(bounds) => match &bounds[0] {
TypeBound::Path(path, _) => Some(
*path.segments().iter().last().unwrap().args_and_bindings.unwrap().bindings
[0]
.type_ref
.as_ref()
.unwrap(),
&TypeBound::Path(path, _) => Some(
*data.types_map[path]
.segments()
.iter()
.last()
.unwrap()
.args_and_bindings
.unwrap()
.bindings[0]
.type_ref
.as_ref()
.unwrap(),
),
_ => None,
},

View file

@ -20,12 +20,11 @@
#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
#![recursion_limit = "512"]
mod semantics;
mod source_analyzer;
mod attrs;
mod from_id;
mod has_source;
mod semantics;
mod source_analyzer;
pub mod db;
pub mod diagnostics;
@ -43,7 +42,7 @@ use arrayvec::ArrayVec;
use base_db::{CrateDisplayName, CrateId, CrateOrigin};
use either::Either;
use hir_def::{
body::{BodyDiagnostic, SyntheticSyntax},
body::BodyDiagnostic,
data::adt::VariantData,
generics::{LifetimeParamData, TypeOrConstParamData, TypeParamProvenance},
hir::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, LabelId, Pat},
@ -54,11 +53,12 @@ use hir_def::{
path::ImportAlias,
per_ns::PerNs,
resolver::{HasResolver, Resolver},
type_ref::TypesSourceMap,
AssocItemId, AssocItemLoc, AttrDefId, CallableDefId, ConstId, ConstParamId, CrateRootModuleId,
DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FunctionId, GenericDefId, GenericParamId,
HasModule, ImplId, InTypeConstId, ItemContainerId, LifetimeParamId, LocalFieldId, Lookup,
MacroExpander, ModuleId, StaticId, StructId, TraitAliasId, TraitId, TupleId, TypeAliasId,
TypeOrConstParamId, TypeParamId, UnionId,
MacroExpander, ModuleId, StaticId, StructId, SyntheticSyntax, TraitAliasId, TraitId, TupleId,
TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId,
};
use hir_expand::{
attrs::collect_attrs, proc_macro::ProcMacroKind, AstId, MacroCallKind, RenderedExpandError,
@ -76,8 +76,8 @@ use hir_ty::{
traits::FnTrait,
AliasTy, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId, GenericArg,
GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, Substitution,
TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, ValueTyDefId,
WhereClause,
TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, TyLoweringDiagnostic,
ValueTyDefId, WhereClause,
};
use itertools::Itertools;
use nameres::diagnostics::DefDiagnosticKind;
@ -89,7 +89,7 @@ use syntax::{
ast::{self, HasAttrs as _, HasGenericParams, HasName},
format_smolstr, AstNode, AstPtr, SmolStr, SyntaxNode, SyntaxNodePtr, TextRange, ToSmolStr, T,
};
use triomphe::Arc;
use triomphe::{Arc, ThinArc};
use crate::db::{DefDatabase, HirDatabase};
@ -147,6 +147,7 @@ pub use {
},
hir_ty::{
consteval::ConstEvalError,
diagnostics::UnsafetyReason,
display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite},
dyn_compatibility::{DynCompatibilityViolation, MethodViolationCode},
layout::LayoutError,
@ -410,6 +411,10 @@ impl ModuleDef {
}
}
if let Some(def) = self.as_self_generic_def() {
def.diagnostics(db, &mut acc);
}
acc
}
@ -430,6 +435,23 @@ impl ModuleDef {
}
}
/// Returns only defs that have generics from themselves, not their parent.
pub fn as_self_generic_def(self) -> Option<GenericDef> {
match self {
ModuleDef::Function(it) => Some(it.into()),
ModuleDef::Adt(it) => Some(it.into()),
ModuleDef::Trait(it) => Some(it.into()),
ModuleDef::TraitAlias(it) => Some(it.into()),
ModuleDef::TypeAlias(it) => Some(it.into()),
ModuleDef::Module(_)
| ModuleDef::Variant(_)
| ModuleDef::Static(_)
| ModuleDef::Const(_)
| ModuleDef::BuiltinType(_)
| ModuleDef::Macro(_) => None,
}
}
pub fn attrs(&self, db: &dyn HirDatabase) -> Option<AttrsWithOwner> {
Some(match self {
ModuleDef::Module(it) => it.attrs(db),
@ -604,17 +626,42 @@ impl Module {
ModuleDef::Adt(adt) => {
match adt {
Adt::Struct(s) => {
let tree_id = s.id.lookup(db.upcast()).id;
let tree_source_maps = tree_id.item_tree_with_source_map(db.upcast()).1;
push_ty_diagnostics(
db,
acc,
db.field_types_with_diagnostics(s.id.into()).1,
tree_source_maps.strukt(tree_id.value).item(),
);
for diag in db.struct_data_with_diagnostics(s.id).1.iter() {
emit_def_diagnostic(db, acc, diag, edition);
}
}
Adt::Union(u) => {
let tree_id = u.id.lookup(db.upcast()).id;
let tree_source_maps = tree_id.item_tree_with_source_map(db.upcast()).1;
push_ty_diagnostics(
db,
acc,
db.field_types_with_diagnostics(u.id.into()).1,
tree_source_maps.union(tree_id.value).item(),
);
for diag in db.union_data_with_diagnostics(u.id).1.iter() {
emit_def_diagnostic(db, acc, diag, edition);
}
}
Adt::Enum(e) => {
for v in e.variants(db) {
let tree_id = v.id.lookup(db.upcast()).id;
let tree_source_maps =
tree_id.item_tree_with_source_map(db.upcast()).1;
push_ty_diagnostics(
db,
acc,
db.field_types_with_diagnostics(v.id.into()).1,
tree_source_maps.variant(tree_id.value),
);
acc.extend(ModuleDef::Variant(v).diagnostics(db, style_lints));
for diag in db.enum_variant_data_with_diagnostics(v.id).1.iter() {
emit_def_diagnostic(db, acc, diag, edition);
@ -625,6 +672,17 @@ impl Module {
acc.extend(def.diagnostics(db, style_lints))
}
ModuleDef::Macro(m) => emit_macro_def_diagnostics(db, acc, m),
ModuleDef::TypeAlias(type_alias) => {
let tree_id = type_alias.id.lookup(db.upcast()).id;
let tree_source_maps = tree_id.item_tree_with_source_map(db.upcast()).1;
push_ty_diagnostics(
db,
acc,
db.type_for_type_alias_with_diagnostics(type_alias.id).1,
tree_source_maps.type_alias(tree_id.value).item(),
);
acc.extend(def.diagnostics(db, style_lints));
}
_ => acc.extend(def.diagnostics(db, style_lints)),
}
}
@ -634,8 +692,11 @@ impl Module {
let mut impl_assoc_items_scratch = vec![];
for impl_def in self.impl_defs(db) {
GenericDef::Impl(impl_def).diagnostics(db, acc);
let loc = impl_def.id.lookup(db.upcast());
let tree = loc.id.item_tree(db.upcast());
let (tree, tree_source_maps) = loc.id.item_tree_with_source_map(db.upcast());
let source_map = tree_source_maps.impl_(loc.id.value).item();
let node = &tree[loc.id.value];
let file_id = loc.id.file_id();
if file_id.macro_file().map_or(false, |it| it.is_builtin_derive(db.upcast())) {
@ -770,6 +831,19 @@ impl Module {
impl_assoc_items_scratch.clear();
}
push_ty_diagnostics(
db,
acc,
db.impl_self_ty_with_diagnostics(impl_def.id).1,
source_map,
);
push_ty_diagnostics(
db,
acc,
db.impl_trait_with_diagnostics(impl_def.id).and_then(|it| it.1),
source_map,
);
for &item in db.impl_data(impl_def.id).items.iter() {
AssocItem::from(item).diagnostics(db, acc, style_lints);
}
@ -1801,6 +1875,25 @@ impl DefWithBody {
let krate = self.module(db).id.krate();
let (body, source_map) = db.body_with_source_map(self.into());
let item_tree_source_maps;
let outer_types_source_map = match self {
DefWithBody::Function(function) => {
let function = function.id.lookup(db.upcast()).id;
item_tree_source_maps = function.item_tree_with_source_map(db.upcast()).1;
item_tree_source_maps.function(function.value).item()
}
DefWithBody::Static(statik) => {
let statik = statik.id.lookup(db.upcast()).id;
item_tree_source_maps = statik.item_tree_with_source_map(db.upcast()).1;
item_tree_source_maps.statik(statik.value)
}
DefWithBody::Const(konst) => {
let konst = konst.id.lookup(db.upcast()).id;
item_tree_source_maps = konst.item_tree_with_source_map(db.upcast()).1;
item_tree_source_maps.konst(konst.value)
}
DefWithBody::Variant(_) | DefWithBody::InTypeConst(_) => &TypesSourceMap::EMPTY,
};
for (_, def_map) in body.blocks(db.upcast()) {
Module { id: def_map.module_id(DefMap::ROOT) }.diagnostics(db, acc, style_lints);
@ -1860,7 +1953,13 @@ impl DefWithBody {
let infer = db.infer(self.into());
for d in &infer.diagnostics {
acc.extend(AnyDiagnostic::inference_diagnostic(db, self.into(), d, &source_map));
acc.extend(AnyDiagnostic::inference_diagnostic(
db,
self.into(),
d,
outer_types_source_map,
&source_map,
));
}
for (pat_or_expr, mismatch) in infer.type_mismatches() {
@ -1890,10 +1989,10 @@ impl DefWithBody {
);
}
let (unafe_exprs, only_lint) = hir_ty::diagnostics::missing_unsafe(db, self.into());
for expr in unafe_exprs {
match source_map.expr_or_pat_syntax(expr) {
Ok(expr) => acc.push(MissingUnsafe { expr, only_lint }.into()),
let (unsafe_exprs, only_lint) = hir_ty::diagnostics::missing_unsafe(db, self.into());
for (node, reason) in unsafe_exprs {
match source_map.expr_or_pat_syntax(node) {
Ok(node) => acc.push(MissingUnsafe { node, only_lint, reason }.into()),
Err(SyntheticSyntax) => {
// FIXME: Here and elsewhere in this file, the `expr` was
// desugared, report or assert that this doesn't happen.
@ -3324,12 +3423,22 @@ impl AssocItem {
) {
match self {
AssocItem::Function(func) => {
GenericDef::Function(func).diagnostics(db, acc);
DefWithBody::from(func).diagnostics(db, acc, style_lints);
}
AssocItem::Const(const_) => {
DefWithBody::from(const_).diagnostics(db, acc, style_lints);
}
AssocItem::TypeAlias(type_alias) => {
GenericDef::TypeAlias(type_alias).diagnostics(db, acc);
let tree_id = type_alias.id.lookup(db.upcast()).id;
let tree_source_maps = tree_id.item_tree_with_source_map(db.upcast()).1;
push_ty_diagnostics(
db,
acc,
db.type_for_type_alias_with_diagnostics(type_alias.id).1,
tree_source_maps.type_alias(tree_id.value).item(),
);
for diag in hir_ty::diagnostics::incorrect_case(db, type_alias.id.into()) {
acc.push(diag.into());
}
@ -3416,6 +3525,97 @@ impl GenericDef {
})
.collect()
}
fn id(self) -> GenericDefId {
match self {
GenericDef::Function(it) => it.id.into(),
GenericDef::Adt(it) => it.into(),
GenericDef::Trait(it) => it.id.into(),
GenericDef::TraitAlias(it) => it.id.into(),
GenericDef::TypeAlias(it) => it.id.into(),
GenericDef::Impl(it) => it.id.into(),
GenericDef::Const(it) => it.id.into(),
}
}
pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>) {
let def = self.id();
let item_tree_source_maps;
let (generics, generics_source_map) = db.generic_params_with_source_map(def);
if generics.is_empty() && generics.no_predicates() {
return;
}
let source_map = match &generics_source_map {
Some(it) => it,
None => match def {
GenericDefId::FunctionId(it) => {
let id = it.lookup(db.upcast()).id;
item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1;
item_tree_source_maps.function(id.value).generics()
}
GenericDefId::AdtId(AdtId::EnumId(it)) => {
let id = it.lookup(db.upcast()).id;
item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1;
item_tree_source_maps.enum_generic(id.value)
}
GenericDefId::AdtId(AdtId::StructId(it)) => {
let id = it.lookup(db.upcast()).id;
item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1;
item_tree_source_maps.strukt(id.value).generics()
}
GenericDefId::AdtId(AdtId::UnionId(it)) => {
let id = it.lookup(db.upcast()).id;
item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1;
item_tree_source_maps.union(id.value).generics()
}
GenericDefId::TraitId(it) => {
let id = it.lookup(db.upcast()).id;
item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1;
item_tree_source_maps.trait_generic(id.value)
}
GenericDefId::TraitAliasId(it) => {
let id = it.lookup(db.upcast()).id;
item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1;
item_tree_source_maps.trait_alias_generic(id.value)
}
GenericDefId::TypeAliasId(it) => {
let id = it.lookup(db.upcast()).id;
item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1;
item_tree_source_maps.type_alias(id.value).generics()
}
GenericDefId::ImplId(it) => {
let id = it.lookup(db.upcast()).id;
item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1;
item_tree_source_maps.impl_(id.value).generics()
}
GenericDefId::ConstId(_) => return,
},
};
push_ty_diagnostics(db, acc, db.generic_defaults_with_diagnostics(def).1, source_map);
push_ty_diagnostics(
db,
acc,
db.generic_predicates_without_parent_with_diagnostics(def).1,
source_map,
);
for (param_id, param) in generics.iter_type_or_consts() {
if let TypeOrConstParamData::ConstParamData(_) = param {
push_ty_diagnostics(
db,
acc,
db.const_param_ty_with_diagnostics(ConstParamId::from_unchecked(
TypeOrConstParamId { parent: def, local_id: param_id },
))
.1,
source_map,
);
}
}
}
}
/// A single local definition.
@ -3581,6 +3781,18 @@ impl Local {
}
}
impl PartialOrd for Local {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Local {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.binding_id.cmp(&other.binding_id)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct DeriveHelper {
pub(crate) derive: MacroId,
@ -5799,3 +6011,19 @@ pub enum DocLinkDef {
Field(Field),
SelfType(Trait),
}
fn push_ty_diagnostics(
db: &dyn HirDatabase,
acc: &mut Vec<AnyDiagnostic>,
diagnostics: Option<ThinArc<(), TyLoweringDiagnostic>>,
source_map: &TypesSourceMap,
) {
if let Some(diagnostics) = diagnostics {
acc.extend(
diagnostics
.slice
.iter()
.filter_map(|diagnostic| AnyDiagnostic::ty_diagnostic(diagnostic, source_map, db)),
);
}
}

View file

@ -34,7 +34,7 @@ use intern::Symbol;
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
use smallvec::{smallvec, SmallVec};
use span::{EditionedFileId, FileId, HirFileIdRepr, SyntaxContextId};
use span::{AstIdMap, EditionedFileId, FileId, HirFileIdRepr, SyntaxContextId};
use stdx::TupleExt;
use syntax::{
algo::skip_trivia_token,
@ -42,6 +42,7 @@ use syntax::{
AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange,
TextSize,
};
use triomphe::Arc;
use crate::{
db::HirDatabase,
@ -509,6 +510,22 @@ impl<'db> SemanticsImpl<'db> {
self.with_ctx(|ctx| ctx.has_derives(adt))
}
pub fn derive_helpers_in_scope(&self, adt: &ast::Adt) -> Option<Vec<(Symbol, Symbol)>> {
let sa = self.analyze_no_infer(adt.syntax())?;
let id = self.db.ast_id_map(sa.file_id).ast_id(adt);
let result = sa
.resolver
.def_map()
.derive_helpers_in_scope(InFile::new(sa.file_id, id))?
.iter()
.map(|(name, macro_, _)| {
let macro_name = Macro::from(*macro_).name(self.db).symbol().clone();
(name.symbol().clone(), macro_name)
})
.collect();
Some(result)
}
pub fn derive_helper(&self, attr: &ast::Attr) -> Option<Vec<(Macro, MacroFileId)>> {
let adt = attr.syntax().ancestors().find_map(ast::Item::cast).and_then(|it| match it {
ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
@ -1500,6 +1517,10 @@ impl<'db> SemanticsImpl<'db> {
self.analyze(path.syntax())?.resolve_path(self.db, path)
}
pub fn resolve_use_type_arg(&self, name: &ast::NameRef) -> Option<TypeParam> {
self.analyze(name.syntax())?.resolve_use_type_arg(name)
}
pub fn resolve_mod_path(
&self,
scope: &SyntaxNode,
@ -1973,10 +1994,16 @@ impl SemanticsScope<'_> {
/// Resolve a path as-if it was written at the given scope. This is
/// necessary a heuristic, as it doesn't take hygiene into account.
pub fn speculative_resolve(&self, ast_path: &ast::Path) -> Option<PathResolution> {
let root = ast_path.syntax().ancestors().last().unwrap();
let ast_id_map = Arc::new(AstIdMap::from_source(&root));
let (mut types_map, mut types_source_map) =
(TypesMap::default(), TypesSourceMap::default());
let mut ctx =
LowerCtx::new(self.db.upcast(), self.file_id, &mut types_map, &mut types_source_map);
let mut ctx = LowerCtx::for_synthetic_ast(
self.db.upcast(),
ast_id_map,
&mut types_map,
&mut types_source_map,
);
let path = Path::from_src(&mut ctx, ast_path.clone())?;
resolve_hir_path(
self.db,
@ -2003,6 +2030,10 @@ impl SemanticsScope<'_> {
)
}
pub fn generic_def(&self) -> Option<crate::GenericDef> {
self.resolver.generic_def().map(|id| id.into())
}
pub fn extern_crates(&self) -> impl Iterator<Item = (Name, Module)> + '_ {
self.resolver.extern_crates_in_scope().map(|(name, id)| (name, Module { id }))
}

View file

@ -36,7 +36,7 @@ use hir_expand::{
use hir_ty::{
diagnostics::{
record_literal_missing_fields, record_pattern_missing_fields, unsafe_expressions,
UnsafeExpr,
InsideUnsafeBlock,
},
lang_items::lang_items_for_bin_op,
method_resolution, Adjustment, InferenceResult, Interner, Substitution, Ty, TyExt, TyKind,
@ -642,6 +642,14 @@ impl SourceAnalyzer {
}
}
pub(crate) fn resolve_use_type_arg(&self, name: &ast::NameRef) -> Option<crate::TypeParam> {
let name = name.as_name();
self.resolver
.all_generic_params()
.find_map(|(params, parent)| params.find_type_by_name(&name, *parent))
.map(crate::TypeParam::from)
}
pub(crate) fn resolve_path(
&self,
db: &dyn HirDatabase,
@ -939,8 +947,8 @@ impl SourceAnalyzer {
*def,
body,
expr_id,
&mut |UnsafeExpr { inside_unsafe_block, .. }| {
is_unsafe |= !inside_unsafe_block
&mut |_, inside_unsafe_block, _| {
is_unsafe |= inside_unsafe_block == InsideUnsafeBlock::No
},
)
};

View file

@ -2318,4 +2318,49 @@ impl<'a> Test<'a, i32> for bool {
"#,
);
}
#[test]
fn issue_17321() {
check_assist(
add_missing_impl_members,
r#"
fn main() {}
mod other_file_1 {
pub const SOME_CONSTANT: usize = 8;
}
mod other_file_2 {
use crate::other_file_1::SOME_CONSTANT;
pub trait Trait {
type Iter: Iterator<Item = [u8; SOME_CONSTANT]>;
}
}
pub struct MyStruct;
impl other_file_2::Trait for MyStruct$0 {}"#,
r#"
fn main() {}
mod other_file_1 {
pub const SOME_CONSTANT: usize = 8;
}
mod other_file_2 {
use crate::other_file_1::SOME_CONSTANT;
pub trait Trait {
type Iter: Iterator<Item = [u8; SOME_CONSTANT]>;
}
}
pub struct MyStruct;
impl other_file_2::Trait for MyStruct {
$0type Iter;
}"#,
);
}
}

Some files were not shown because too many files have changed in this diff Show more