Trait Speculative
trait Speculative
Extensions to the ParseStream API to support speculative parsing.
Required Methods
fn advance_to(self: &Self, fork: &Self)Advance this parse stream to the position of a forked parse stream.
This is the opposite operation to
ParseStream::fork. You can fork a parse stream, perform some speculative parsing, then join the original stream to the fork to "commit" the parsing from the fork to the main stream.If you can avoid doing this, you should, as it limits the ability to generate useful errors. That said, it is often the only way to parse syntax of the form
A* B*for arbitrary syntaxAandB. The problem is that when the fork fails to parse anA, it's impossible to tell whether that was because of a syntax error and the user meant to provide anA, or that theAs are finished and it's time to start parsingBs. Use with care.Also note that if
Ais a subset ofB,A* B*can be parsed by parsingB*and removing the leading members ofAfrom the repetition, bypassing the need to involve the downsides associated with speculative parsing.Example
There has been chatter about the possibility of making the colons in the turbofish syntax like
path::to::<T>no longer required by acceptingpath::to<T>in expression position. Specifically, according to RFC 2544,PathSegmentparsing should always try to consume a following<token as the start of generic arguments, and reset to the<if that fails (e.g. the token is acting as a less-than operator).This is the exact kind of parsing behavior which requires the "fork, try, commit" behavior that
ParseStream::forkdiscourages. Withadvance_to, we can avoid having to parse the speculatively parsed content a second time.This change in behavior can be implemented in syn by replacing just the
Parseimplementation forPathSegment:# use IdentExt; use Speculative; # use ; # use ; # # # .unwrap;Drawbacks
The main drawback of this style of speculative parsing is in error presentation. Even if the lookahead is the "correct" parse, the error that is shown is that of the "fallback" parse. To use the same example as the turbofish above, take the following unfinished "turbofish":
let _ = f<&'a fn(), for<'a> serde::>();If this is parsed as generic arguments, we can provide the error message
error: expected identifier --> src.rs:L:C | L | let _ = f<&'a fn(), for<'a> serde::>(); | ^but if parsed using the above speculative parsing, it falls back to assuming that the
<is a less-than when it fails to parse the generic arguments, and tries to interpret the&'aas the start of a labelled loop, resulting in the much less helpful errorerror: expected `:` --> src.rs:L:C | L | let _ = f<&'a fn(), for<'a> serde::>(); | ^^This can be mitigated with various heuristics (two examples: show both forks' parse errors, or show the one that consumed more tokens), but when you can control the grammar, sticking to something that can be parsed LL(3) and without the LL(*) speculative parsing this makes possible, displaying reasonable errors becomes much more simple.
Performance
This method performs a cheap fixed amount of work that does not depend on how far apart the two streams are positioned.
Panics
The forked stream in the argument of
advance_tomust have been obtained by forkingself. Attempting to advance to any other stream will cause a panic.
Implementors
impl<'a> Speculative for crate::parse::ParseBuffer<'a>