Skip to content

Commit 609ed99

Browse files
nikomatsakisNiko Matsakis
authored andcommitted
rework how variable/id ambiguity works
1 parent b65bb73 commit 609ed99

File tree

5 files changed

+159
-36
lines changed

5 files changed

+159
-36
lines changed

crates/formality-core/src/parse.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ pub struct SuccessfulParse<'t, T> {
8181
/// to `parse_variant`.
8282
precedence: Precedence,
8383

84+
/// Did this result from parsing an in-scope variable? If so,
85+
/// we prefer this parse to other parses that consumed the same set of
86+
/// input tokens.
87+
is_in_scope_var: bool,
88+
8489
/// The value produced.
8590
value: T,
8691
}
@@ -99,6 +104,7 @@ impl<'t, T> SuccessfulParse<'t, T> {
99104
text: self.text,
100105
reductions: self.reductions,
101106
precedence: self.precedence,
107+
is_in_scope_var: self.is_in_scope_var,
102108
value: op(self.value),
103109
}
104110
}
@@ -114,6 +120,7 @@ where
114120
text: term.text,
115121
reductions: term.reductions,
116122
precedence: term.precedence,
123+
is_in_scope_var: term.is_in_scope_var,
117124
value: term.value.upcast(),
118125
}
119126
}

crates/formality-core/src/parse/parser.rs

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ where
153153
current_text: &'t str,
154154
reductions: Vec<&'static str>,
155155
is_cast_variant: bool,
156+
is_in_scope_var: bool,
156157

157158
/// A variant is 'committed' when we have seen enough success
158159
is_committed: bool,
@@ -232,6 +233,20 @@ where
232233
})
233234
}
234235

236+
pub fn parse_variant_variable(
237+
&mut self,
238+
variant_name: &'static str,
239+
variant_precedence: Precedence,
240+
kind: L::Kind,
241+
) where
242+
CoreVariable<L>: Upcast<T>,
243+
{
244+
Self::parse_variant(self, variant_name, variant_precedence, |p| {
245+
p.mark_as_in_scope_var();
246+
p.variable_of_kind(kind)
247+
})
248+
}
249+
235250
/// Parses a single variant for this nonterminal.
236251
/// The name of the variant is significant and will be tracked as part of the list of reductions.
237252
/// The precedence is part of how we resolve conflicts: if there are two successful parses with distinct precedence, higher precedence wins.
@@ -272,7 +287,7 @@ where
272287

273288
match result {
274289
Ok(value) => {
275-
// Subtle: for cast variants, don't record the variant name in the reduction lits,
290+
// Subtle: for cast variants, don't record the variant name in the reduction list,
276291
// as it doesn't carry semantic weight. See `mark_as_cast_variant` for more details.
277292
if !active_variant.is_cast_variant {
278293
active_variant.reductions.push(variant_name);
@@ -282,6 +297,7 @@ where
282297
text: active_variant.current_text,
283298
reductions: active_variant.reductions,
284299
precedence: variant_precedence,
300+
is_in_scope_var: active_variant.is_in_scope_var,
285301
value: value.upcast(),
286302
});
287303
tracing::trace!("success: {:?}", self.successes.last().unwrap());
@@ -369,6 +385,9 @@ where
369385
}
370386

371387
has_prefix(&s_i.reductions, &s_j.reductions)
388+
|| (s_i.is_in_scope_var
389+
&& !s_j.is_in_scope_var
390+
&& skip_whitespace(s_i.text) == skip_whitespace(s_j.text))
372391
}
373392
}
374393

@@ -385,6 +404,7 @@ where
385404
current_text: start_text,
386405
reductions: vec![],
387406
is_cast_variant: false,
407+
is_in_scope_var: false,
388408
is_committed: true,
389409
}
390410
}
@@ -481,6 +501,16 @@ where
481501
self.is_cast_variant = true;
482502
}
483503

504+
/// Indicates that this variant is parsing *only* an in-scope variable.
505+
/// We have special treatment for disambiguation such that if a variant marked
506+
/// as parsing an "in-scope variable" and some other variant both consume the
507+
/// same part of the input string, we prefer the variable. This method is automatically
508+
/// invoked on `#[variable]` variants by the generated parser code and you generally
509+
/// wouldn't want to call it yourself.
510+
pub fn mark_as_in_scope_var(&mut self) {
511+
self.is_in_scope_var = true;
512+
}
513+
484514
/// Expect *exactly* the given text (after skipping whitespace)
485515
/// in the input string. Reports an error if anything else is observed.
486516
/// In error case, consumes only whitespace.
@@ -569,6 +599,7 @@ where
569599
reductions: vec![],
570600
scope: self.scope,
571601
is_cast_variant: false,
602+
is_in_scope_var: false,
572603
is_committed: true,
573604
};
574605

@@ -629,6 +660,7 @@ where
629660
current_text: self.current_text,
630661
reductions: vec![],
631662
is_cast_variant: false,
663+
is_in_scope_var: false,
632664
is_committed: true,
633665
};
634666
let result = op(&mut av);
@@ -637,21 +669,6 @@ where
637669
result
638670
}
639671

640-
/// Returns an error if an in-scope variable name is found.
641-
/// The derive automatically inserts calls to this for all other variants
642-
/// if any variant is declared `#[variable]`.
643-
pub fn reject_variable(&self) -> Result<(), Set<ParseError<'t>>> {
644-
self.reject::<CoreVariable<L>>(
645-
|p| p.variable(),
646-
|var| {
647-
ParseError::at(
648-
self.current_text,
649-
format!("found unexpected in-scope variable {:?}", var),
650-
)
651-
},
652-
)
653-
}
654-
655672
/// Parses the next identifier as a variable in scope.
656673
///
657674
/// **NB:** This departs from the limits of context-free
@@ -873,6 +890,7 @@ where
873890
text,
874891
reductions,
875892
precedence: _,
893+
is_in_scope_var: _,
876894
value,
877895
} = left_recursion::recurse(self.current_state(), || op(self.scope, self.current_text))?;
878896

crates/formality-macros/src/parse.rs

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use syn::{spanned::Spanned, Attribute};
77
use synstructure::BindingInfo;
88

99
use crate::{
10-
attrs::{has_cast_attr, has_variable_attr, precedence, variable},
10+
attrs::{has_cast_attr, precedence, variable},
1111
spec::{self, FieldMode, FormalitySpec},
1212
variable::Variable,
1313
};
@@ -40,17 +40,10 @@ pub(crate) fn derive_parse_with_spec(
4040
}
4141
}
4242

43-
// Determine whether *any* variant is marked as `#[variable]`.
44-
// If so, all *other* variants will automatically reject in-scope variable names.
45-
let any_variable_variant = s
46-
.variants()
47-
.iter()
48-
.any(|v| has_variable_attr(v.ast().attrs));
49-
5043
let mut parse_variants = TokenStream::new();
5144
for variant in s.variants() {
5245
let variant_name = Literal::string(&format!("{}::{}", s.ast().ident, variant.ast().ident));
53-
let v = parse_variant(variant, external_spec, any_variable_variant)?;
46+
let v = parse_variant(variant, external_spec)?;
5447
let precedence = precedence(variant.ast().attrs)?.expr();
5548
parse_variants.extend(quote_spanned!(
5649
variant.ast().ident.span() =>
@@ -76,19 +69,11 @@ pub(crate) fn derive_parse_with_spec(
7669
fn parse_variant(
7770
variant: &synstructure::VariantInfo,
7871
external_spec: Option<&FormalitySpec>,
79-
any_variable_variant: bool,
8072
) -> syn::Result<TokenStream> {
8173
let ast = variant.ast();
8274
let variable_attr = variable(ast.attrs)?;
8375
let mut stream = TokenStream::default();
8476

85-
// If there are variable variants -- but this is not one -- then we always begin by rejecting
86-
// variable names. This avoids ambiguity in a case like `for<ty X> { X : Debug }`, where we
87-
// want to parse `X` as a type variable.
88-
if any_variable_variant && variable_attr.is_none() {
89-
stream.extend(quote_spanned!(ast.ident.span() => __p.reject_variable()?;));
90-
}
91-
9277
// When invoked like `#[term(foo)]`, use the spec from `foo`
9378
if let Some(spec) = external_spec {
9479
return parse_variant_with_attr(variant, spec, stream);
@@ -124,6 +109,7 @@ fn parse_variant(
124109
let construct = variant.construct(field_ident);
125110
stream.extend(quote_spanned! {
126111
ast.ident.span() =>
112+
__p.mark_as_in_scope_var();
127113
let #v0 = __p.variable_of_kind(#kind)?;
128114
Ok(#construct)
129115
});

crates/formality-types/src/grammar/ty/parse_impls.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,7 @@ fn parse_parameters<'t>(
123123
impl CoreParse<Rust> for ConstData {
124124
fn parse<'t>(scope: &Scope<Rust>, text: &'t str) -> ParseResult<'t, Self> {
125125
Parser::multi_variant(scope, text, "ConstData", |parser| {
126-
parser.parse_variant("Variable", Precedence::default(), |p| {
127-
p.variable_of_kind(ParameterKind::Const)
128-
});
126+
parser.parse_variant_variable("Variable", Precedence::default(), ParameterKind::Const);
129127

130128
parser.parse_variant_cast::<Bool>(Precedence::default());
131129

tests/parser_var_id_ambiguity.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//! Test for how we handle ambiguity between a variable and an identifier in the parser.
2+
//! This can't be part of parser-torture-tests because it requires settting up a custom variable.
3+
4+
use std::sync::Arc;
5+
6+
// Default language for our crate
7+
use formality_core::{language::HasKind, term};
8+
use ptt::grammar::{Binder, BoundVar, ExistentialVar, UniversalVar, Variable};
9+
use ptt::FormalityLang;
10+
11+
formality_core::declare_language! {
12+
mod ptt {
13+
const NAME = "PTT";
14+
type Kind = crate::Kind;
15+
type Parameter = crate::Parameter;
16+
const BINDING_OPEN = '<';
17+
const BINDING_CLOSE = '>';
18+
const KEYWORDS = [
19+
];
20+
}
21+
}
22+
23+
#[term]
24+
#[derive(Copy)]
25+
pub enum Kind {
26+
Ty,
27+
}
28+
29+
#[term]
30+
pub enum Parameter {
31+
#[cast]
32+
Ty(Ty),
33+
}
34+
35+
#[term]
36+
pub enum Ty {
37+
#[variable(Kind::Ty)]
38+
Variable(Variable),
39+
40+
#[cast]
41+
Id(Id),
42+
43+
#[grammar($v0 :: $v1)]
44+
Assoc(Arc<Ty>, Id),
45+
}
46+
47+
formality_core::id!(Id);
48+
49+
formality_core::cast_impl!((BoundVar) <: (Variable) <: (Ty));
50+
formality_core::cast_impl!((ExistentialVar) <: (Variable) <: (Ty));
51+
formality_core::cast_impl!((UniversalVar) <: (Variable) <: (Ty));
52+
formality_core::cast_impl!((Variable) <: (Ty) <: (Parameter));
53+
formality_core::cast_impl!((BoundVar) <: (Ty) <: (Parameter));
54+
formality_core::cast_impl!((ExistentialVar) <: (Ty) <: (Parameter));
55+
formality_core::cast_impl!((UniversalVar) <: (Ty) <: (Parameter));
56+
57+
impl HasKind<FormalityLang> for Parameter {
58+
fn kind(&self) -> formality_core::language::CoreKind<FormalityLang> {
59+
match self {
60+
Parameter::Ty(_) => Kind::Ty,
61+
}
62+
}
63+
}
64+
65+
#[test]
66+
fn parse_var() {
67+
let value: Binder<Ty> = ptt::term("<ty T> T");
68+
expect_test::expect![[r#"
69+
Binder {
70+
kinds: [
71+
Ty,
72+
],
73+
term: Variable(
74+
^ty0_0,
75+
),
76+
}
77+
"#]]
78+
.assert_debug_eq(&value);
79+
}
80+
81+
#[test]
82+
fn parse_id() {
83+
let value: Binder<Ty> = ptt::term("<ty T> i32");
84+
expect_test::expect![[r#"
85+
Binder {
86+
kinds: [
87+
Ty,
88+
],
89+
term: Id(
90+
i32,
91+
),
92+
}
93+
"#]]
94+
.assert_debug_eq(&value);
95+
}
96+
97+
#[test]
98+
fn parse_assoc() {
99+
let value: Binder<Ty> = ptt::term("<ty T> i32::T");
100+
expect_test::expect![[r#"
101+
Binder {
102+
kinds: [
103+
Ty,
104+
],
105+
term: Assoc(
106+
Id(
107+
i32,
108+
),
109+
T,
110+
),
111+
}
112+
"#]]
113+
.assert_debug_eq(&value);
114+
}

0 commit comments

Comments
 (0)