|
2 | 2 |
|
3 | 3 | #![allow(clippy::module_name_repetitions)]
|
4 | 4 |
|
5 |
| -use crate::line_span; |
| 5 | +use crate::{get_parent_expr, line_span}; |
6 | 6 | use rustc_errors::Applicability;
|
7 |
| -use rustc_hir::{Expr, ExprKind}; |
| 7 | +use rustc_hir::{BinOpKind, Expr, ExprKind, MatchSource}; |
8 | 8 | use rustc_lint::{LateContext, LintContext};
|
9 | 9 | use rustc_span::hygiene;
|
10 | 10 | use rustc_span::{BytePos, Pos, Span, SyntaxContext};
|
@@ -306,6 +306,199 @@ pub fn snippet_with_context(
|
306 | 306 | )
|
307 | 307 | }
|
308 | 308 |
|
| 309 | +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] |
| 310 | +pub enum ExprPosition { |
| 311 | + // Also includes `return`, `yield`, `break` and closures |
| 312 | + Paren, |
| 313 | + AssignmentRhs, |
| 314 | + AssignmentLhs, |
| 315 | + RangeLhs, |
| 316 | + RangeRhs, |
| 317 | + OrLhs, |
| 318 | + OrRhs, |
| 319 | + AndLhs, |
| 320 | + AndRhs, |
| 321 | + Let, |
| 322 | + EqLhs, |
| 323 | + EqRhs, |
| 324 | + BitOrLhs, |
| 325 | + BitOrRhs, |
| 326 | + BitXorLhs, |
| 327 | + BitXorRhs, |
| 328 | + BitAndLhs, |
| 329 | + BitAndRhs, |
| 330 | + ShiftLhs, |
| 331 | + ShiftRhs, |
| 332 | + AddLhs, |
| 333 | + AddRhs, |
| 334 | + MulLhs, |
| 335 | + MulRhs, |
| 336 | + // Also includes type ascription |
| 337 | + Cast, |
| 338 | + Prefix, |
| 339 | + Postfix, |
| 340 | +} |
| 341 | + |
| 342 | +/// Extracts a snippet of the given expression taking into account the `SyntaxContext` the snippet |
| 343 | +/// needs to be taken from. Parenthesis will be added if needed to place the snippet in the target |
| 344 | +/// precedence level. Returns a placeholder (`(..)`) if a snippet can't be extracted (e.g. an |
| 345 | +/// invalid span). |
| 346 | +/// |
| 347 | +/// The `SyntaxContext` of the expression will be walked up to the given target context (usually |
| 348 | +/// from the parent expression) before extracting a snippet. This allows getting the call to a macro |
| 349 | +/// rather than the expression from expanding the macro. e.g. In the expression `&vec![]` taking a |
| 350 | +/// snippet of the chile of the borrow expression will get a snippet of what `vec![]` expands in to. |
| 351 | +/// With the target context set to the same as the borrow expression, this will get a snippet of the |
| 352 | +/// call to the macro. |
| 353 | +/// |
| 354 | +/// The applicability will be modified in two ways: |
| 355 | +/// * If a snippet can't be extracted it will be changed from `MachineApplicable` or |
| 356 | +/// `MaybeIncorrect` to `HasPlaceholders`. |
| 357 | +/// * If the snippet is taken from a macro expansion then it will be changed from |
| 358 | +/// `MachineApplicable` to `MaybeIncorrect`. |
| 359 | +pub fn snippet_expr( |
| 360 | + cx: &LateContext<'_>, |
| 361 | + expr: &Expr<'_>, |
| 362 | + target_position: ExprPosition, |
| 363 | + ctxt: SyntaxContext, |
| 364 | + app: &mut Applicability, |
| 365 | +) -> String { |
| 366 | + let (snip, is_mac_call) = snippet_with_context(cx, expr.span, ctxt, "(..)", app); |
| 367 | + |
| 368 | + match snip { |
| 369 | + Cow::Borrowed(snip) => snip.to_owned(), |
| 370 | + Cow::Owned(snip) if is_mac_call => snip, |
| 371 | + Cow::Owned(mut snip) => { |
| 372 | + let ctxt = expr.span.ctxt(); |
| 373 | + |
| 374 | + // Attempt to determine if parenthesis are needed base on the target position. The snippet may have |
| 375 | + // parenthesis already, so attempt to find those. |
| 376 | + // TODO: Remove parenthesis if they aren't needed at the target position. |
| 377 | + let needs_paren = match expr.peel_drop_temps().kind { |
| 378 | + ExprKind::Binary(_, lhs, rhs) |
| 379 | + if (ctxt == lhs.span.ctxt() && expr.span.lo() != lhs.span.lo()) |
| 380 | + || (ctxt == rhs.span.ctxt() && expr.span.hi() != rhs.span.hi()) => |
| 381 | + { |
| 382 | + false |
| 383 | + }, |
| 384 | + ExprKind::Binary(op, ..) => match op.node { |
| 385 | + BinOpKind::Add | BinOpKind::Sub => target_position > ExprPosition::AddLhs, |
| 386 | + BinOpKind::Mul | BinOpKind::Div | BinOpKind::Rem => target_position > ExprPosition::MulLhs, |
| 387 | + BinOpKind::And => target_position > ExprPosition::AndLhs, |
| 388 | + BinOpKind::Or => target_position > ExprPosition::OrLhs, |
| 389 | + BinOpKind::BitXor => target_position > ExprPosition::BitXorLhs, |
| 390 | + BinOpKind::BitAnd => target_position > ExprPosition::BitAndLhs, |
| 391 | + BinOpKind::BitOr => target_position > ExprPosition::BitOrLhs, |
| 392 | + BinOpKind::Shl | BinOpKind::Shr => target_position > ExprPosition::ShiftLhs, |
| 393 | + BinOpKind::Eq | BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ne | BinOpKind::Gt | BinOpKind::Ge => { |
| 394 | + target_position > ExprPosition::EqLhs |
| 395 | + }, |
| 396 | + }, |
| 397 | + ExprKind::Box(..) | ExprKind::Unary(..) | ExprKind::AddrOf(..) if snip.starts_with('(') => false, |
| 398 | + ExprKind::Box(..) | ExprKind::Unary(..) | ExprKind::AddrOf(..) => { |
| 399 | + target_position > ExprPosition::Prefix |
| 400 | + }, |
| 401 | + ExprKind::Let(..) if snip.starts_with('(') => false, |
| 402 | + ExprKind::Let(..) => target_position > ExprPosition::Let, |
| 403 | + ExprKind::Cast(lhs, rhs) |
| 404 | + if (ctxt == lhs.span.ctxt() && expr.span.lo() != lhs.span.lo()) |
| 405 | + || (ctxt == rhs.span.ctxt() && expr.span.hi() != rhs.span.hi()) => |
| 406 | + { |
| 407 | + false |
| 408 | + }, |
| 409 | + ExprKind::Cast(..) | ExprKind::Type(..) => target_position > ExprPosition::Cast, |
| 410 | + |
| 411 | + ExprKind::Closure(..) |
| 412 | + | ExprKind::Break(..) |
| 413 | + | ExprKind::Ret(..) |
| 414 | + | ExprKind::Yield(..) |
| 415 | + | ExprKind::Assign(..) |
| 416 | + | ExprKind::AssignOp(..) => target_position > ExprPosition::AssignmentRhs, |
| 417 | + |
| 418 | + // Postfix operators, or expression with braces of some form |
| 419 | + ExprKind::Array(_) |
| 420 | + | ExprKind::Call(..) |
| 421 | + | ExprKind::ConstBlock(_) |
| 422 | + | ExprKind::MethodCall(..) |
| 423 | + | ExprKind::Tup(..) |
| 424 | + | ExprKind::Lit(..) |
| 425 | + | ExprKind::DropTemps(_) |
| 426 | + | ExprKind::If(..) |
| 427 | + | ExprKind::Loop(..) |
| 428 | + | ExprKind::Match(..) |
| 429 | + | ExprKind::Block(..) |
| 430 | + | ExprKind::Field(..) |
| 431 | + | ExprKind::Index(..) |
| 432 | + | ExprKind::Path(_) |
| 433 | + | ExprKind::Continue(_) |
| 434 | + | ExprKind::InlineAsm(_) |
| 435 | + | ExprKind::LlvmInlineAsm(_) |
| 436 | + | ExprKind::Struct(..) |
| 437 | + | ExprKind::Repeat(..) |
| 438 | + | ExprKind::Err => false, |
| 439 | + }; |
| 440 | + |
| 441 | + if needs_paren { |
| 442 | + snip.insert(0, '('); |
| 443 | + snip.push(')'); |
| 444 | + } |
| 445 | + snip |
| 446 | + }, |
| 447 | + } |
| 448 | +} |
| 449 | + |
| 450 | +/// Gets which position the expression is in relative to it's parent. Defaults to `Paren` if the |
| 451 | +/// parent node is not an expression. |
| 452 | +pub fn position_of_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> ExprPosition { |
| 453 | + match get_parent_expr(cx, expr) { |
| 454 | + None => ExprPosition::Paren, |
| 455 | + Some(parent) => match parent.kind { |
| 456 | + ExprKind::DropTemps(_) => position_of_expr(cx, parent), |
| 457 | + ExprKind::Binary(op, lhs, _) => match (op.node, expr.hir_id == lhs.hir_id) { |
| 458 | + (BinOpKind::Add | BinOpKind::Sub, true) => ExprPosition::AddLhs, |
| 459 | + (BinOpKind::Add | BinOpKind::Sub, false) => ExprPosition::AddRhs, |
| 460 | + (BinOpKind::Mul | BinOpKind::Div | BinOpKind::Rem, true) => ExprPosition::MulLhs, |
| 461 | + (BinOpKind::Mul | BinOpKind::Div | BinOpKind::Rem, false) => ExprPosition::MulRhs, |
| 462 | + (BinOpKind::And, true) => ExprPosition::AndLhs, |
| 463 | + (BinOpKind::And, false) => ExprPosition::AndRhs, |
| 464 | + (BinOpKind::Or, true) => ExprPosition::OrLhs, |
| 465 | + (BinOpKind::Or, false) => ExprPosition::OrRhs, |
| 466 | + (BinOpKind::BitXor, true) => ExprPosition::BitXorLhs, |
| 467 | + (BinOpKind::BitXor, false) => ExprPosition::BitXorRhs, |
| 468 | + (BinOpKind::BitAnd, true) => ExprPosition::BitAndLhs, |
| 469 | + (BinOpKind::BitAnd, false) => ExprPosition::BitAndRhs, |
| 470 | + (BinOpKind::BitOr, true) => ExprPosition::BitOrLhs, |
| 471 | + (BinOpKind::BitOr, false) => ExprPosition::BitOrRhs, |
| 472 | + (BinOpKind::Shl | BinOpKind::Shr, true) => ExprPosition::ShiftLhs, |
| 473 | + (BinOpKind::Shl | BinOpKind::Shr, false) => ExprPosition::ShiftRhs, |
| 474 | + ( |
| 475 | + BinOpKind::Eq | BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ne | BinOpKind::Gt | BinOpKind::Ge, |
| 476 | + true, |
| 477 | + ) => ExprPosition::EqLhs, |
| 478 | + ( |
| 479 | + BinOpKind::Eq | BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ne | BinOpKind::Gt | BinOpKind::Ge, |
| 480 | + false, |
| 481 | + ) => ExprPosition::EqRhs, |
| 482 | + }, |
| 483 | + ExprKind::Unary(..) | ExprKind::AddrOf(..) => ExprPosition::Prefix, |
| 484 | + ExprKind::Cast(..) | ExprKind::Type(..) => ExprPosition::Cast, |
| 485 | + ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == expr.hir_id => { |
| 486 | + ExprPosition::AssignmentLhs |
| 487 | + }, |
| 488 | + ExprKind::Assign(..) | ExprKind::AssignOp(..) => ExprPosition::AssignmentRhs, |
| 489 | + ExprKind::Call(e, _) | ExprKind::MethodCall(_, _, [e, ..], _) | ExprKind::Index(e, _) |
| 490 | + if expr.hir_id == e.hir_id => |
| 491 | + { |
| 492 | + ExprPosition::Postfix |
| 493 | + }, |
| 494 | + ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar) | ExprKind::Field(..) => { |
| 495 | + ExprPosition::Postfix |
| 496 | + }, |
| 497 | + _ => ExprPosition::Paren, |
| 498 | + }, |
| 499 | + } |
| 500 | +} |
| 501 | + |
309 | 502 | /// Walks the span up to the target context, thereby returning the macro call site if the span is
|
310 | 503 | /// inside a macro expansion, or the original span if it is not. Note this will return `None` in the
|
311 | 504 | /// case of the span being in a macro expansion, but the target context is from expanding a macro
|
|
0 commit comments