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

Bounds parsing refactoring #37511

Closed
wants to merge 2 commits into from

Conversation

matklad
Copy link
Member

@matklad matklad commented Nov 1, 2016

A follow up of #37278.

parse_opt_ty_param_bounds is introduced to make sure that we don't parse empty bonds. This fixes a couple of bugs along the way, where the check was missing. This is [breaking-change].

Require at least one predicate for a lifetime in a where clause
@rust-highfive
Copy link
Collaborator

r? @pnkfelix

(rust_highfive has picked a reviewer for you, use r? to override)

bound_lifetimes: bound_lifetimes,
bounded_ty: bounded_ty,
bounds: bounds,
}));

parsed_something = true;
} else if self.eat(&token::Eq) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason I can't use else if here: #37510 :(

}

let bounds = self.parse_ty_param_bounds(mode)?;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather remove plain parse_ty_param_bounds and leave only the opt version, but unfortunately this wont work for parse_impl_trait_type, because it used a keyword (impl) as an introducing_token, and you can't eat a keyword token. Or can you?

Copy link
Contributor

@jseyfried jseyfried Nov 11, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or can you?

parser.eat(&token::Ident(keywords::Impl.ident())) should work.

@bluss
Copy link
Member

bluss commented Nov 1, 2016

This kind of change would normally first have warnings, then enable the breaking change later.

@matklad
Copy link
Member Author

matklad commented Nov 1, 2016

Yep, a warning is a good idea. But perhaps we need a crater run first, to estimate the amount of breakage just in case? Or is crater capable of reporting a diff in warnings?

@bluss
Copy link
Member

bluss commented Nov 1, 2016

A deny by default warning works well just for crater (how it was done in /pull/37378 ). That also helps to only catch the actual crates that regress.

@matklad
Copy link
Member Author

matklad commented Nov 2, 2016

And can I emit a deny by default warning directly from the parser? I believe I can't do this in ast_validation.rs in a straightforward way, because it's impossible to distinguish absence of bounds from the empty bounds, both case produce AST of the same shape. I think I can look a the spans and try to determine if bounds are empty, but : is present, but this feels a bit too hacky.

An alternative which I do know should work is to run crater with span.error and then change to span.warn. warn_missing_semicolon produces a similar warning now.

@matklad
Copy link
Member Author

matklad commented Nov 7, 2016

@pnkfelix ping :)

@alexcrichton alexcrichton added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Nov 10, 2016
@alexcrichton
Copy link
Member

@jseyfried you may also be able to take a look and help with review

@jseyfried jseyfried assigned jseyfried and unassigned pnkfelix Nov 11, 2016
at least one bound in it");
}
if let Some(bounds) = self.parse_opt_ty_param_bounds(&token::Colon,
BoundParsingMode::Bare)? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you refactor this to

let opt_bounds = self.parse_opt_ty_param_bounds(&token::Colon, BoundParsingMode::Bare)?;
if let Some(bounds) = opt_bounds {

then you can continue using else if below.

@jseyfried
Copy link
Contributor

If I understand correctly, there are three separate [breaking-change]s here:

  • where 'a, ... is no longer allowed. I believe it equivalent to where ... today.
  • where 'a 'b, ... is no longer allowed. Today, it is equivalent to where 'a: 'b, ....
  • We require a type parameter bound after : or + (in the appropriate context). Today, a bound is not required in the two cases from empty-type-parameter-bounds.rs.

Could someone do a Crater run to determine which of these changes need a warning cycle?
cc @eddyb

@jseyfried jseyfried added the S-waiting-on-crater Status: Waiting on a crater run to be completed. label Nov 11, 2016
@petrochenkov
Copy link
Contributor

petrochenkov commented Nov 11, 2016

We require a type parameter bound after : or +

I haven't read the patch, but always requiring type after + seems incorrect, it's a usual case of trailing separator, they are always allowed (except for trailing | in match arms).

@jseyfried
Copy link
Contributor

I think of + not as a separator but as a associative operator -- e.g. let x = 1 +; wouldn't make sense.

@eddyb
Copy link
Member

eddyb commented Nov 11, 2016

Started crater run (sorry for the delay).

@eddyb
Copy link
Member

eddyb commented Nov 11, 2016

Crater report shows 8 regressions (only eetf is a false positive).
Most of them have something like trait Provider: { which is just weird.
However, the ones which generate empty lists of bounds through macros seem legitimate.

@eddyb eddyb removed the S-waiting-on-crater Status: Waiting on a crater run to be completed. label Nov 11, 2016
@jseyfried
Copy link
Contributor

jseyfried commented Nov 11, 2016

@eddyb I think a macro generating empty lists of bounds could have a legitimate use case, but it looks like the macro-expanded bounds from the Crater run are always empty and can be easily avoided. Also, we already forbid empty lists of bounds in other contexts today.

@matklad The all the breakage appears to be due to empty bounds lists after :. Could you make this a warning instead of a hard error?

@petrochenkov
Copy link
Contributor

petrochenkov commented Nov 12, 2016

I've audited ast.rs and it turns out there are very few lists where empty list is syntactically invalid.

  1. Path segments. Empty path may make sense semantically - current module if it's local or crate root if it's global, but there are obvious parsing problems.
  2. Patterns in match arm. A match arm without patterns (impossible arm) doesn't make sense semantically.
  3. Type or lifetime bounds. Empty list is semantically valid (no bounds) and may be generated by macros.
  4. Where predicates. Empty list is semantically valid (no predicates) and may be generated by macros.

Basically, I think this should be valid, even if it looks somewhat weird:

struct S<T:> where {
    field: T,
}

@petrochenkov
Copy link
Contributor

petrochenkov commented Nov 12, 2016

@jseyfried

I think of + not as a separator but as a associative operator -- e.g. let x = 1 +; wouldn't make sense.

I admit it may be interpreted as addition in type sums like type T = Trait + Send;, but it's certainly a list separator in bounds.
If both cases are parsed by the same routine now, then I don't mind prohibiting trailing +, it's not a big deal and can always be allowed later if convincing use case is found.

@matklad
Copy link
Member Author

matklad commented Nov 12, 2016

Looks like it is not clear if empty parameter bounds should be allowed. So I've resurrected #37278 which only fixes a clear bug with lifetimes in the where clause.

So I think we need to tag this with T-lang and decide what syntax should be allowed. Here is the status quo:

// These are accepted
trait A: {}
fn b<'a:, U:>() {}
type C = for<'a> Clone+;

// These are forbidden

// bounds on where clauses must be non empty
fn d<T>() where T: {}

// In type grammar, `+` is treated like a binary operator,
// and hence both L and R side are required.
fn e(f: &(A+)) {}

@jseyfried
Copy link
Contributor

I'm leaning toward @petrochenkov's proposal to allow empty bounds lists (e.g. fn f<T:>() {}).
I don't have a strong opinion trailing +s, which aren't as important since we can forbid them consistently without without breakage in practice and decide later.

@rust-lang/lang do we want to allow empty bounds lists and/or trailing +s? Both are sometimes allowed today. Forbidding empty bounds lists would cause some trivially fixable breakage, so we would need a warning cycle.

@jseyfried jseyfried added I-nominated T-lang Relevant to the language team, which will review and decide on the PR/issue. and removed T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Nov 14, 2016
@bors
Copy link
Contributor

bors commented Nov 14, 2016

☔ The latest upstream changes (presumably #37278) made this pull request unmergeable. Please resolve the merge conflicts.

@nrc
Copy link
Member

nrc commented Nov 15, 2016

IMO we should allow neither empty bounds lists nor trailing + both are weird. However, I'm somewhat sympathetic to the macro/codegen argument for the bounds list case.

@matklad
Copy link
Member Author

matklad commented Nov 15, 2016

Worth mentioning that currently where clauses allow empty bounds for lifetimes, but not for types.

Valid: where 'a:, T: Foo

Invalid: where 'a: 'b, T:

@nikomatsakis
Copy link
Contributor

At the @rust-lang/lang meeting recently we settled on:

  • accept empty bounds list for both types/lifetime parameters
  • do not accept trailing + for now because @nrc thinks it looks weird
    • macros can instead generate multiple where-clauses:
      • T: Foo, T: Bar,

Sound good?

@matklad
Copy link
Member Author

matklad commented Dec 5, 2016

Sound good?

Yep. Stuff like trait Provider: { can be cleaned up by IDE / rustfmt.

@pnkfelix
Copy link
Member

@matklad does the current PR encode that semantics yet, or does it need further revision?

@matklad
Copy link
Member Author

matklad commented Dec 23, 2016

@pnkfelix yep, this does not implement the suggestion yet. I'll try to look into that, thanks for the reminder!

@matklad
Copy link
Member Author

matklad commented Jan 10, 2017

I'll try to look into that, thanks for the reminder!

Looks like I don't have enough free time after all to actually make a fix :(

@petrochenkov
Copy link
Contributor

petrochenkov commented Jan 11, 2017

@matklad
I can rebase this and finish the work if you want (not right now though, but in a couple of weeks actually maybe sooner).

@matklad
Copy link
Member Author

matklad commented Jan 11, 2017

@petrochenkov that'd be great! Not sure that rebasing makes sense though: the current implementation is rather different from the final proposal we arrived at!

@petrochenkov
Copy link
Contributor

#39158 is submitted.

@aturon
Copy link
Member

aturon commented Jan 26, 2017

Should we close this PR in favor of #39158?

@matklad
Copy link
Member Author

matklad commented Jan 26, 2017

Surely!

@matklad matklad closed this Jan 26, 2017
bors added a commit that referenced this pull request Jan 27, 2017
Bounds parsing refactoring 2

See #37511 for previous discussion.
cc @matklad

Relaxed parsing rules:
 - zero bounds after `:` are allowed in all contexts.
 - zero predicates are allowed after `where`.
- trailing separator `,` is allowed after predicates in `where` clauses not followed by `{`.

Other parsing rules:
 - trailing separator `+` is still allowed in all bound lists.

Code is also cleaned up and tests added.

I haven't touched parsing of trait object types yet, I'll do it later.
@matklad matklad deleted the bounds-parsing-refactoring branch July 9, 2019 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.