Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggest enclosing const expression in block #64700

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions src/librustc_resolve/late.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1494,14 +1494,15 @@ impl<'a, 'b> LateResolutionVisitor<'a, '_> {
);
}

fn smart_resolve_path_fragment(&mut self,
id: NodeId,
qself: Option<&QSelf>,
path: &[Segment],
span: Span,
source: PathSource<'_>,
crate_lint: CrateLint)
-> PartialRes {
fn smart_resolve_path_fragment(
&mut self,
id: NodeId,
qself: Option<&QSelf>,
path: &[Segment],
span: Span,
source: PathSource<'_>,
crate_lint: CrateLint,
) -> PartialRes {
let ns = source.namespace();
let is_expected = &|res| source.is_expected(res);

Expand Down Expand Up @@ -1620,9 +1621,10 @@ impl<'a, 'b> LateResolutionVisitor<'a, '_> {
match self.resolve_qpath(id, qself, path, ns, span, crate_lint) {
// If defer_to_typeck, then resolution > no resolution,
// otherwise full resolution > partial resolution > no resolution.
Some(partial_res) if partial_res.unresolved_segments() == 0 ||
defer_to_typeck =>
return Some(partial_res),
Some(partial_res)
if partial_res.unresolved_segments() == 0 || defer_to_typeck => {
return Some(partial_res)
}
partial_res => if fin_res.is_none() { fin_res = partial_res },
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/libsyntax/parse/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ use std::path::PathBuf;

bitflags::bitflags! {
struct Restrictions: u8 {
const STMT_EXPR = 1 << 0;
const NO_STRUCT_LITERAL = 1 << 1;
const STMT_EXPR = 1 << 0;
const NO_STRUCT_LITERAL = 1 << 1;
const CLOSING_ANGLE_BRACKET = 1 << 2;
}
}

Expand Down
32 changes: 31 additions & 1 deletion src/libsyntax/parse/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,15 @@ impl<'a> Parser<'a> {
self.last_type_ascription = None;
return Ok(lhs);
}
(true, Some(AssocOp::Greater)) |
(true, Some(AssocOp::Less)) |
(true, Some(AssocOp::ShiftLeft)) |
(true, Some(AssocOp::ShiftRight))
if self.restrictions.contains(Restrictions::CLOSING_ANGLE_BRACKET) => {
// Bare `const` argument expression.
self.last_type_ascription = None;
return Ok(lhs);
}
(true, Some(_)) => {
// We've found an expression that would be parsed as a statement, but the next
// token implies this should be parsed as an expression.
Expand All @@ -205,6 +214,15 @@ impl<'a> Parser<'a> {
}
self.expected_tokens.push(TokenType::Operator);
while let Some(op) = AssocOp::from_token(&self.token) {
if let (true, AssocOp::Greater) |
(true, AssocOp::Less) |
(true, AssocOp::ShiftLeft) |
(true, AssocOp::ShiftRight) = (
self.restrictions.contains(Restrictions::CLOSING_ANGLE_BRACKET), &op
) {
// Bare `const` argument expression fully parsed.
return Ok(lhs);
}

// Adjust the span for interpolated LHS to point to the `$lhs` token and not to what
// it refers to. Interpolated identifiers are unwrapped early and never show up here
Expand Down Expand Up @@ -341,8 +359,20 @@ impl<'a> Parser<'a> {

/// Checks if this expression is a successfully parsed statement.
fn expr_is_complete(&self, e: &Expr) -> bool {
self.restrictions.contains(Restrictions::STMT_EXPR) &&
(
self.restrictions.contains(Restrictions::STMT_EXPR) &&
!classify::expr_requires_semi_to_be_stmt(e)
) || (
self.restrictions.contains(Restrictions::CLOSING_ANGLE_BRACKET) &&
// Comparison and bitshift operators are not allowed in bare const expressions.
[
token::Lt,
token::Gt,
token::Comma,
token::BinOp(token::Shl),
token::BinOp(token::Shr),
].contains(&self.token.kind)
)
}

fn is_at_start_of_range_notation_rhs(&self) -> bool {
Expand Down
129 changes: 98 additions & 31 deletions src/libsyntax/parse/parser/path.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use super::{Parser, PResult, TokenType};
use super::{Parser, PResult, Restrictions, TokenType};

use crate::{maybe_whole, ThinVec};
use crate::ast::{self, QSelf, Path, PathSegment, Ident, ParenthesizedArgs, AngleBracketedArgs};
use crate::ast::{AnonConst, GenericArg, AssocTyConstraint, AssocTyConstraintKind, BlockCheckMode};
use crate::ast::{
self, AngleBracketedArgs, AnonConst, AssocTyConstraint, AssocTyConstraintKind, BlockCheckMode,
GenericArg, Ident, ParenthesizedArgs, Path, PathSegment, QSelf,
};
use crate::parse::token::{self, Token};
use crate::source_map::{Span, BytePos};
use crate::symbol::kw;
Expand Down Expand Up @@ -321,7 +323,7 @@ impl<'a> Parser<'a> {
};

debug!("parse_generic_args_with_leading_angle_bracket_recovery: (snapshotting)");
match self.parse_generic_args() {
match self.parse_generic_args(style) {
Ok(value) => Ok(value),
Err(ref mut e) if is_first_invocation && self.unmatched_angle_bracket_count > 0 => {
// Cancel error from being unable to find `>`. We know the error
Expand Down Expand Up @@ -368,15 +370,18 @@ impl<'a> Parser<'a> {
.emit();

// Try again without unmatched angle bracket characters.
self.parse_generic_args()
self.parse_generic_args(style)
},
Err(e) => Err(e),
}
}

/// Parses (possibly empty) list of lifetime and type arguments and associated type bindings,
/// possibly including trailing comma.
fn parse_generic_args(&mut self) -> PResult<'a, (Vec<GenericArg>, Vec<AssocTyConstraint>)> {
fn parse_generic_args(
&mut self,
style: PathStyle,
) -> PResult<'a, (Vec<GenericArg>, Vec<AssocTyConstraint>)> {
let mut args = Vec::new();
let mut constraints = Vec::new();
let mut misplaced_assoc_ty_constraints: Vec<Span> = Vec::new();
Expand Down Expand Up @@ -413,34 +418,13 @@ impl<'a> Parser<'a> {
span,
});
assoc_ty_constraints.push(span);
} else if self.check_possible_const_needing_braces(style) {
args.push(self.parse_const_or_type_arg()?);
misplaced_assoc_ty_constraints.append(&mut assoc_ty_constraints);
} else if self.check_const_arg() {
// Parse const argument.
let expr = if let token::OpenDelim(token::Brace) = self.token.kind {
self.parse_block_expr(
None, self.token.span, BlockCheckMode::Default, ThinVec::new()
)?
} else if self.token.is_ident() {
// FIXME(const_generics): to distinguish between idents for types and consts,
// we should introduce a GenericArg::Ident in the AST and distinguish when
// lowering to the HIR. For now, idents for const args are not permitted.
if self.token.is_bool_lit() {
self.parse_literal_maybe_minus()?
} else {
return Err(
self.fatal("identifiers may currently not be used for const generics")
);
}
} else {
self.parse_literal_maybe_minus()?
};
let value = AnonConst {
id: ast::DUMMY_NODE_ID,
value: expr,
};
args.push(GenericArg::Const(value));
args.push(self.parse_const_arg()?);
misplaced_assoc_ty_constraints.append(&mut assoc_ty_constraints);
} else if self.check_type() {
// Parse type argument.
args.push(GenericArg::Type(self.parse_ty()?));
misplaced_assoc_ty_constraints.append(&mut assoc_ty_constraints);
} else {
Expand Down Expand Up @@ -471,4 +455,87 @@ impl<'a> Parser<'a> {

Ok((args, constraints))
}

fn parse_const_arg(&mut self) -> PResult<'a, GenericArg> {
let value = if let token::OpenDelim(token::Brace) = self.token.kind {
// Parse `const` argument surrounded by braces.
self.parse_block_expr(None, self.token.span, BlockCheckMode::Default, ThinVec::new())?
} else {
self.parse_expr_res(Restrictions::CLOSING_ANGLE_BRACKET, None)?
};
let value = AnonConst { id: ast::DUMMY_NODE_ID, value };
Ok(GenericArg::Const(value))
}

/// Check some ambiguous cases between type and non-block const arguments.
fn check_possible_const_needing_braces(&mut self, style: PathStyle) -> bool {
style == PathStyle::Expr && (
self.token.kind == token::Not || // `foo::<!const_bool_fn()>()`
self.token.kind == token::OpenDelim(token::Paren) // `foo::<(1, 2, 3)>()`
)
}

/// There's intent ambiguity for this argument, it could be a const expression missing braces,
/// or it could be a type argument. We try the later as the grammar expects, and if it fails we
/// attempt the former and emit a targetted suggestion if valid.
fn parse_const_or_type_arg(&mut self) -> PResult<'a, GenericArg> {
let mut snapshot = self.clone();
match self.parse_ty() {
// Nothing to do, this was indeed a type argument.
Ok(ty) if [
token::Comma,
token::Gt,
token::BinOp(token::Shr),
].contains(&self.token.kind) => Ok(GenericArg::Type(ty)),
Ok(ty) => { // Could have found `foo::<!false>()`, needs `foo::<{ !false }>()`.
mem::swap(self, &mut snapshot);
match self.parse_bad_const_arg() {
Ok(arg) => Ok(arg),
Err(mut err) => {
mem::swap(self, &mut snapshot);
err.cancel();
Ok(GenericArg::Type(ty))
}
}
}
Err(mut ty_err) => {
mem::swap(self, &mut snapshot);
match self.parse_bad_const_arg() {
Ok(arg) => {
ty_err.cancel();
Ok(arg)
}
Err(mut err) => {
mem::swap(self, &mut snapshot);
err.cancel();
Err(ty_err)
}
}
}
}
}

fn parse_bad_const_arg(&mut self) -> PResult<'a, GenericArg> {
let msg = if self.token.kind == token::OpenDelim(token::Paren) {
// `foo::<(1, 2, 3)>()`
"tuples in const arguments must be surrounded by braces"
} else {
"complex const arguments must be surrounded by braces"
};
match self.parse_const_arg() {
Ok(arg) => {
self.span_fatal(arg.span(), msg)
.multipart_suggestion(
"surround this const argument in braces",
vec![
(arg.span().shrink_to_lo(), "{ ".to_string()),
(arg.span().shrink_to_hi(), " }".to_string()),
],
Applicability::MachineApplicable,
).emit();
Ok(arg)
}
Err(err) => Err(err),
}
}
}
8 changes: 6 additions & 2 deletions src/libsyntax/parse/parser/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ impl<'a> Parser<'a> {
}
}

pub(super) fn parse_ty_common(&mut self, allow_plus: bool, allow_qpath_recovery: bool,
allow_c_variadic: bool) -> PResult<'a, P<Ty>> {
pub(super) fn parse_ty_common(
&mut self,
allow_plus: bool,
allow_qpath_recovery: bool,
allow_c_variadic: bool,
) -> PResult<'a, P<Ty>> {
maybe_recover_from_interpolated_ty_qpath!(self, allow_qpath_recovery);
maybe_whole!(self, NtTy, |x| x);

Expand Down
68 changes: 58 additions & 10 deletions src/test/ui/const-generics/const-expression-parameter.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,70 @@
#![feature(const_generics)]
//~^ WARN the feature `const_generics` is incomplete and may cause the compiler to crash

fn i32_identity<const X: i32>() -> i32 {
5
const fn const_i32() -> i32 {
42
}

fn foo_a() {
i32_identity::<-1>(); // ok
const fn const_bool() -> bool {
true
}
const X: i32 = 1;

fn foo_b() {
i32_identity::<1 + 2>(); //~ ERROR expected one of `,` or `>`, found `+`
fn foo<const X: i32>() -> i32 { 5 }

fn baz<const X: i32, const Y: i32>() { }

fn bar<const X: bool>() {}

fn bat<const X: (i32, i32, i32)>() {}

fn main() {
foo::<-1>(); // ok
foo::<1 + 2>(); // ok
foo::< -1 >(); // ok
foo::<1 + 2, 3 + 4>(); //~ ERROR wrong number of const arguments: expected 1, found 2
foo::<5>(); // ok
foo::< const_i32() >(); //~ ERROR expected type, found function `const_i32`
//~^ ERROR wrong number of const arguments: expected 1, found 0
//~| ERROR wrong number of type arguments: expected 0, found 1
foo::< X >(); //~ ERROR expected type, found constant `X`
//~^ ERROR wrong number of const arguments: expected 1, found 0
//~| ERROR wrong number of type arguments: expected 0, found 1
foo::<{ X }>(); // ok
foo::< 42 + X >(); // ok
foo::<{ const_i32() }>(); // ok

baz::<-1, -2>(); // ok
baz::<1 + 2, 3 + 4>(); // ok
baz::< -1 , 2 >(); // ok
baz::< -1 , "2" >(); //~ ERROR mismatched types

bat::<(1, 2, 3)>(); //~ ERROR tuples in const arguments must be surrounded by braces
bat::<(1, 2)>();
//~^ ERROR tuples in const arguments must be surrounded by braces
//~| ERROR mismatched types

bar::<false>(); // ok
bar::<!false>(); //~ ERROR complex const arguments must be surrounded by braces
bar::<{ const_bool() }>(); // ok
bar::< const_bool() >(); //~ ERROR expected type, found function `const_bool`
//~^ ERROR wrong number of const arguments: expected 1, found 0
//~| ERROR wrong number of type arguments: expected 0, found 1
bar::<{ !const_bool() }>(); // ok
bar::< !const_bool() >(); //~ ERROR complex const arguments must be surrounded by braces

foo::<foo::<42>()>(); //~ ERROR expected one of `!`, `+`, `,`, `::`, or `>`, found `(`
}

fn foo_c() {
i32_identity::< -1 >(); // ok
fn parse_err_1() {
bar::< 3 < 4 >(); //~ ERROR expected one of `,`, `.`, `>`, or `?`, found `<`
}

fn main() {
i32_identity::<5>(); // ok
fn parse_err_2() {
foo::< const_i32() + 42 >();
//~^ ERROR expected one of `!`, `(`, `,`, `>`, `?`, `for`, lifetime, or path, found `42`
}
fn parse_err_3() {
foo::< X + 42 >();
//~^ ERROR expected one of `!`, `(`, `,`, `>`, `?`, `for`, lifetime, or path, found `42`
}
Loading