From 1dab6f14f9f50db2d3a6fa44c42e2cc0f78a8485 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 4 Nov 2023 18:16:28 -0400 Subject: [PATCH 01/38] move the Variable names etc into a grammar module This permits `use foo::grammar::*` --- crates/formality-core/src/lib.rs | 49 +++++++++++++++--------- crates/formality-rust/src/prove.rs | 7 +--- crates/formality-types/src/grammar.rs | 5 +-- crates/formality-types/src/grammar/ty.rs | 4 +- 4 files changed, 36 insertions(+), 29 deletions(-) diff --git a/crates/formality-core/src/lib.rs b/crates/formality-core/src/lib.rs index 53e54a13..63d416c7 100644 --- a/crates/formality-core/src/lib.rs +++ b/crates/formality-core/src/lib.rs @@ -95,18 +95,27 @@ macro_rules! declare_language { ) => { $(#[$the_lang_m:meta])* $the_lang_v mod $the_lang { - use super::*; + use $crate::language::Language; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] pub struct FormalityLang; - impl $crate::language::Language for FormalityLang { - const NAME: &'static str = $name; - type Kind = $kind; - type Parameter = $param; - const BINDING_OPEN: char = $binding_open; - const BINDING_CLOSE: char = $binding_close; - const KEYWORDS: &'static [&'static str] = &[$($kw),*]; + // This module may seem weird -- it permits us to import `super::*` + // so that all the types in `$kind` and `$param` are valid without + // importing `super::*` into the entire module. This not only makes + // things a bit nicer, since those imports are not needed and could + // cause weird behavior, it avoids a cycle when users + // do `pub use $lang::grammar::*`. + mod __hygiene { + use super::super::*; + impl $crate::language::Language for super::FormalityLang { + const NAME: &'static str = $name; + type Kind = $kind; + type Parameter = $param; + const BINDING_OPEN: char = $binding_open; + const BINDING_CLOSE: char = $binding_close; + const KEYWORDS: &'static [&'static str] = &[$($kw),*]; + } } $crate::trait_alias! { @@ -125,15 +134,19 @@ macro_rules! declare_language { pub trait Term = $crate::term::CoreTerm } - pub type Variable = $crate::variable::CoreVariable; - pub type ExistentialVar = $crate::variable::CoreExistentialVar; - pub type UniversalVar = $crate::variable::CoreUniversalVar; - pub type BoundVar = $crate::variable::CoreBoundVar; - pub type DebruijnIndex = $crate::variable::DebruijnIndex; - pub type VarIndex = $crate::variable::VarIndex; - pub type Binder = $crate::binder::CoreBinder; - pub type Substitution = $crate::substitution::CoreSubstitution; - pub type VarSubstitution = $crate::substitution::CoreVarSubstitution; + /// Grammar items to be included in this language. + pub mod grammar { + use super::FormalityLang; + pub type Variable = $crate::variable::CoreVariable; + pub type ExistentialVar = $crate::variable::CoreExistentialVar; + pub type UniversalVar = $crate::variable::CoreUniversalVar; + pub type BoundVar = $crate::variable::CoreBoundVar; + pub type DebruijnIndex = $crate::variable::DebruijnIndex; + pub type VarIndex = $crate::variable::VarIndex; + pub type Binder = $crate::binder::CoreBinder; + pub type Substitution = $crate::substitution::CoreSubstitution; + pub type VarSubstitution = $crate::substitution::CoreVarSubstitution; + } /// Parses `text` as a term with no bindings in scope. #[track_caller] @@ -161,7 +174,7 @@ macro_rules! declare_language { pub fn term_with(bindings: impl IntoIterator, text: &str) -> $crate::Fallible where T: Parse, - B: $crate::Upcast<(String, $param)>, + B: $crate::Upcast<(String, ::Parameter)>, { $crate::parse::core_term_with::(bindings, text) } diff --git a/crates/formality-rust/src/prove.rs b/crates/formality-rust/src/prove.rs index e9193567..f7bc8996 100644 --- a/crates/formality-rust/src/prove.rs +++ b/crates/formality-rust/src/prove.rs @@ -6,11 +6,8 @@ use crate::grammar::{ }; use formality_core::{seq, Set, To, Upcast, Upcasted}; use formality_prove as prove; -use formality_types::{ - grammar::{ - AdtId, AliasTy, Binder, ParameterKind, Predicate, Relation, TraitId, Ty, Wc, Wcs, PR, - }, - rust::BoundVar, +use formality_types::grammar::{ + AdtId, AliasTy, Binder, BoundVar, ParameterKind, Predicate, Relation, TraitId, Ty, Wc, Wcs, PR, }; impl Program { diff --git a/crates/formality-types/src/grammar.rs b/crates/formality-types/src/grammar.rs index 285576f6..34df24e3 100644 --- a/crates/formality-types/src/grammar.rs +++ b/crates/formality-types/src/grammar.rs @@ -5,10 +5,7 @@ mod kinded; mod ty; mod wc; -pub use crate::rust::{ - Binder, BoundVar, DebruijnIndex, ExistentialVar, Substitution, UniversalVar, VarIndex, - VarSubstitution, Variable, -}; +pub use crate::rust::grammar::*; pub use consts::*; pub use formulas::*; pub use ids::*; diff --git a/crates/formality-types/src/grammar/ty.rs b/crates/formality-types/src/grammar/ty.rs index 0d4580d3..66b1904d 100644 --- a/crates/formality-types/src/grammar/ty.rs +++ b/crates/formality-types/src/grammar/ty.rs @@ -4,11 +4,11 @@ use std::sync::Arc; mod debug_impls; mod parse_impls; mod term_impls; -use crate::rust::{BoundVar, Variable}; use formality_core::{DowncastTo, To, Upcast, UpcastFrom}; use super::{ - consts::Const, AdtId, AssociatedItemId, Binder, ExistentialVar, FnId, TraitId, UniversalVar, + consts::Const, AdtId, AssociatedItemId, Binder, BoundVar, ExistentialVar, FnId, TraitId, + UniversalVar, Variable, }; #[term] From 51ccd0cc582275be4dbe6c9e90458b3318627af3 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 4 Nov 2023 18:32:24 -0400 Subject: [PATCH 02/38] start an example --- examples/formality-eg/grammar.rs | 123 +++++++++++++++++++++++++++ examples/formality-eg/main.rs | 26 ++++++ examples/formality-eg/type_system.rs | 0 3 files changed, 149 insertions(+) create mode 100644 examples/formality-eg/grammar.rs create mode 100644 examples/formality-eg/main.rs create mode 100644 examples/formality-eg/type_system.rs diff --git a/examples/formality-eg/grammar.rs b/examples/formality-eg/grammar.rs new file mode 100644 index 00000000..64896429 --- /dev/null +++ b/examples/formality-eg/grammar.rs @@ -0,0 +1,123 @@ +use formality_core::{cast_impl, language::HasKind, term}; +use std::sync::Arc; + +pub use crate::eg::grammar::*; + +#[term] +#[derive(Copy)] +pub enum Kind { + #[grammar(type)] + Ty, +} + +#[term] +pub enum Parameter { + #[cast] + Ty(Ty), +} + +#[term] +pub enum Ty { + Integer, + + #[cast] + StructTy(StructTy), + + #[variable] + Var(Variable), +} + +#[term($id < $,parameters >)] +pub struct StructTy { + pub id: StructId, + pub parameters: Vec, +} + +#[term(struct $id $bound)] +pub struct StructDecl { + id: StructId, + bound: Binder, +} + +#[term({ $*fields })] +pub struct StructBoundData { + fields: Vec, +} + +#[term($name : $ty)] +pub struct FieldDecl { + name: FieldId, + ty: Ty, +} + +#[term(fn $id $bound)] +pub struct FnDecl { + id: FnId, + bound: Binder, +} + +#[term(($,fn_parameters) -> $return_ty { $body })] +pub struct FnBoundData { + pub fn_parameters: Vec, + pub return_ty: Ty, + pub body: Expr, +} + +#[term($id : $ty)] +pub struct LocalVariableDecl { + pub id: LocalVarId, + pub ty: Ty, +} + +#[term] +pub enum Expr { + #[cast] + LocalVar(LocalVarId), + + #[cast] + IntegerLiteral(usize), + + #[grammar($v0 { $,v1 })] + StructLiteral(StructTy, Vec), + + #[grammar($v0 + $v1)] + Add(Arc, Arc), + + #[grammar($v0 - $v1)] + Sub(Arc, Arc), + + #[grammar($v0 * $v1)] + #[precedence(1)] + Mul(Arc, Arc), + + #[grammar($v0 / $v1)] + #[precedence(1)] + Div(Arc, Arc), + + #[grammar(let $v0 = $v1 in $v2)] + LetIn(LocalVarId, Arc, Arc), +} + +#[term($id : $expr)] +pub struct FieldExpr { + pub id: FieldId, + pub expr: Expr, +} + +formality_core::id!(StructId); +formality_core::id!(FieldId); +formality_core::id!(FnId); +formality_core::id!(LocalVarId); + +cast_impl!((Variable) <: (Ty) <: (Parameter)); +cast_impl!((BoundVar) <: (Variable) <: (Parameter)); +cast_impl!((ExistentialVar) <: (Variable) <: (Parameter)); +cast_impl!((UniversalVar) <: (Variable) <: (Parameter)); + +impl HasKind for Parameter { + fn kind(&self) -> Kind { + match self { + Parameter::Ty(_) => Kind::Ty, + } + } +} diff --git a/examples/formality-eg/main.rs b/examples/formality-eg/main.rs new file mode 100644 index 00000000..273dbd77 --- /dev/null +++ b/examples/formality-eg/main.rs @@ -0,0 +1,26 @@ +mod grammar; +mod type_system; + +formality_core::declare_language! { + mod eg { + const NAME = "Eg"; + type Kind = crate::grammar::Kind; + type Parameter = crate::grammar::Parameter; + const BINDING_OPEN = '<'; + const BINDING_CLOSE = '>'; + const KEYWORDS = [ + "struct", + "fn", + "let", + "in", + ]; + } +} + +// Default language for our crate +use eg::FormalityLang; +use formality_core::Fallible; + +fn main() -> Fallible<()> { + Ok(()) +} diff --git a/examples/formality-eg/type_system.rs b/examples/formality-eg/type_system.rs new file mode 100644 index 00000000..e69de29b From 7292feb33cc196d3774a6dcb75c00f4bcefcf378 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 4 Nov 2023 20:04:45 -0400 Subject: [PATCH 03/38] eg: tests --- examples/formality-eg/grammar.rs | 5 ++++- examples/formality-eg/grammar/test.rs | 32 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 examples/formality-eg/grammar/test.rs diff --git a/examples/formality-eg/grammar.rs b/examples/formality-eg/grammar.rs index 64896429..fcaad4fc 100644 --- a/examples/formality-eg/grammar.rs +++ b/examples/formality-eg/grammar.rs @@ -3,6 +3,9 @@ use std::sync::Arc; pub use crate::eg::grammar::*; +#[cfg(test)] +mod test; + #[term] #[derive(Copy)] pub enum Kind { @@ -39,7 +42,7 @@ pub struct StructDecl { bound: Binder, } -#[term({ $*fields })] +#[term({ $,fields })] pub struct StructBoundData { fields: Vec, } diff --git a/examples/formality-eg/grammar/test.rs b/examples/formality-eg/grammar/test.rs new file mode 100644 index 00000000..5a706e11 --- /dev/null +++ b/examples/formality-eg/grammar/test.rs @@ -0,0 +1,32 @@ +use formality_core::test; + +use crate::eg::term; + +use super::{Expr, StructDecl, Ty}; + +#[test] +fn test_struct_decl() { + let r: StructDecl = term("struct Point<> { x: integer, y: integer }"); + expect_test::expect![[r#" + struct Point <> { x : integer, y : integer } + "#]] + .assert_debug_eq(&r); +} + +#[test] +fn test_struct_ty() { + let r: Ty = term("Point<>"); + expect_test::expect![[r#" + Point < > + "#]] + .assert_debug_eq(&r); +} + +#[test] +fn test_vec_int_ty() { + let r: Ty = term("Vec"); + expect_test::expect![[r#" + Vec < integer > + "#]] + .assert_debug_eq(&r); +} From 44abbf2e94ea1e084e8ab5ceb62669b5ee6104aa Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 4 Nov 2023 20:26:18 -0400 Subject: [PATCH 04/38] refactor parsing of modes to be more extensible The old code was kind of specific to `$ . F` where `.` is a mode. This is going to allow us to accommodate new modes more easily. --- crates/formality-macros/src/spec.rs | 78 ++++++++++++++++++----------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/crates/formality-macros/src/spec.rs b/crates/formality-macros/src/spec.rs index 23c1cf80..3c701632 100644 --- a/crates/formality-macros/src/spec.rs +++ b/crates/formality-macros/src/spec.rs @@ -110,43 +110,63 @@ fn parse_variable_binding( dollar_token: Punct, tokens: &mut impl Iterator, ) -> syn::Result { - let error = || { - let message = "expected field name or field mode (`,`, `*`)"; - Err(syn::Error::new(dollar_token.span(), message)) - }; + let mut tokens = tokens.peekable(); - let mut next_token = match tokens.next() { - Some(v) => v, - None => return error(), + let Some(token) = tokens.peek() else { + return error(dollar_token); }; - // If there is a field mode (e.g., `*`) present, parse it. - let mode = match next_token { - TokenTree::Ident(_) => FieldMode::Single, - TokenTree::Punct(punct) => { - let mode = match punct.as_char() { - ',' => FieldMode::Comma, - '*' => FieldMode::Many, - '?' => FieldMode::Optional, - '$' => return Ok(FormalitySpecSymbol::Char { punct }), - _ => return error(), - }; + return match token { + // $x + TokenTree::Ident(_) => { + parse_variable_binding_name(dollar_token, FieldMode::Single, &mut tokens) + } - next_token = match tokens.next() { - Some(v) => v, - None => return error(), + // $$ + TokenTree::Punct(punct) if punct.as_char() == '$' => { + let Some(TokenTree::Punct(punct)) = tokens.next() else { + unreachable!() }; + Ok(FormalitySpecSymbol::Char { punct }) + } - mode + // $,x + TokenTree::Punct(punct) if punct.as_char() == ',' => { + tokens.next(); + parse_variable_binding_name(dollar_token, FieldMode::Comma, &mut tokens) + } + + // $*x + TokenTree::Punct(punct) if punct.as_char() == '*' => { + tokens.next(); + parse_variable_binding_name(dollar_token, FieldMode::Many, &mut tokens) + } + + // $?x + TokenTree::Punct(punct) if punct.as_char() == '?' => { + tokens.next(); + parse_variable_binding_name(dollar_token, FieldMode::Optional, &mut tokens) } - TokenTree::Group(_) | TokenTree::Literal(_) => return error(), - }; - // Extract the name of the field. - let name = match next_token { - TokenTree::Ident(name) => name, - _ => return error(), + _ => error(dollar_token), }; - Ok(FormalitySpecSymbol::Field { name, mode }) + fn parse_variable_binding_name( + dollar_token: Punct, + mode: FieldMode, + tokens: &mut impl Iterator, + ) -> syn::Result { + // Extract the name of the field. + let name = match tokens.next() { + Some(TokenTree::Ident(name)) => name, + _ => return error(dollar_token), + }; + + Ok(FormalitySpecSymbol::Field { name, mode }) + } + + fn error(dollar_token: Punct) -> syn::Result { + let message = "expected field name or field mode (`,`, `*`)"; + Err(syn::Error::new(dollar_token.span(), message)) + } } From edc9d48a950c878d2a5afcbef4a20c30a179fb6f Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 4 Nov 2023 22:49:32 -0400 Subject: [PATCH 05/38] introduce delimited lists We now support optional lists. --- crates/formality-core/src/parse.rs | 13 ++- crates/formality-core/src/parse/parser.rs | 27 +++++ crates/formality-macros/src/debug.rs | 28 ++++++ crates/formality-macros/src/parse.rs | 16 +++ crates/formality-macros/src/spec.rs | 114 +++++++++++++++++++--- examples/formality-eg/grammar.rs | 4 +- examples/formality-eg/grammar/test.rs | 6 +- examples/formality-eg/main.rs | 1 + 8 files changed, 185 insertions(+), 24 deletions(-) diff --git a/crates/formality-core/src/parse.rs b/crates/formality-core/src/parse.rs index 3170e45c..7ae0aa68 100644 --- a/crates/formality-core/src/parse.rs +++ b/crates/formality-core/src/parse.rs @@ -222,10 +222,7 @@ where { fn parse<'t>(scope: &Scope, text: &'t str) -> ParseResult<'t, Self> { Parser::single_variant(scope, text, "Vec", |p| { - p.expect_char('[')?; - let v = p.comma_nonterminal()?; - p.expect_char(']')?; - Ok(v) + p.delimited_nonterminal('[', false, ']') }) } } @@ -277,7 +274,13 @@ where { fn parse<'t>(scope: &Scope, text: &'t str) -> ParseResult<'t, Self> { Parser::single_variant(scope, text, "Binder", |p| { - p.expect_char(L::BINDING_OPEN)?; + match p.expect_char(L::BINDING_OPEN) { + Ok(()) => {} + Err(_) => { + return Ok(CoreBinder::dummy(p.nonterminal()?)); + } + } + let bindings: Vec> = p.comma_nonterminal()?; p.expect_char(L::BINDING_CLOSE)?; diff --git a/crates/formality-core/src/parse/parser.rs b/crates/formality-core/src/parse/parser.rs index 1224455e..a8618cec 100644 --- a/crates/formality-core/src/parse/parser.rs +++ b/crates/formality-core/src/parse/parser.rs @@ -527,6 +527,33 @@ where Ok(result) } + #[tracing::instrument(level = "Trace", ret)] + pub fn delimited_nonterminal( + &mut self, + open: char, + optional: bool, + close: char, + ) -> Result, Set>> + where + T: CoreParse, + { + // Look for the opening delimiter. + // If we don't find it, then this is either an empty vector (if optional) or an error (otherwise). + match self.expect_char(open) { + Ok(()) => {} + Err(errs) => { + return if optional { Ok(vec![]) } else { Err(errs) }; + } + } + + // Now parse the contents. + let result = self.comma_nonterminal()?; + + self.expect_char(close)?; + + Ok(result) + } + /// Parse multiple instances of `T` separated by commas. #[track_caller] pub fn comma_nonterminal(&mut self) -> Result, Set>> diff --git a/crates/formality-macros/src/debug.rs b/crates/formality-macros/src/debug.rs index ec0241c3..c96825bf 100644 --- a/crates/formality-macros/src/debug.rs +++ b/crates/formality-macros/src/debug.rs @@ -194,6 +194,34 @@ fn debug_variant_with_attr( } } + spec::FormalitySpecSymbol::Field { + name, + mode: + FieldMode::Delimited { + open, + optional, + close, + }, + } => { + let open = Literal::character(*open); + let close = Literal::character(*close); + quote_spanned! { + name.span() => + if !#optional || !#name.is_empty() { + write!(fmt, "{}", sep)?; + write!(fmt, "{}", #open)?; + sep = ""; + for e in #name { + write!(fmt, "{}", sep)?; + write!(fmt, "{:?}", e)?; + sep = ", "; + } + write!(fmt, "{}", #close)?; + sep = " "; + } + } + } + spec::FormalitySpecSymbol::Keyword { ident } => { let literal = as_literal(ident); quote_spanned!(ident.span() => diff --git a/crates/formality-macros/src/parse.rs b/crates/formality-macros/src/parse.rs index bd05ac29..95de1297 100644 --- a/crates/formality-macros/src/parse.rs +++ b/crates/formality-macros/src/parse.rs @@ -179,6 +179,22 @@ fn parse_variant_with_attr( } } + spec::FormalitySpecSymbol::Field { + name, + mode: + FieldMode::Delimited { + open, + optional, + close, + }, + } => { + let open = Literal::character(*open); + let close = Literal::character(*close); + quote_spanned! { + name.span() => + let #name = __p.delimited_nonterminal(#open, #optional, #close)?; + } + } spec::FormalitySpecSymbol::Field { name, mode: FieldMode::Comma, diff --git a/crates/formality-macros/src/spec.rs b/crates/formality-macros/src/spec.rs index 3c701632..555d3673 100644 --- a/crates/formality-macros/src/spec.rs +++ b/crates/formality-macros/src/spec.rs @@ -1,4 +1,7 @@ -use proc_macro2::{Ident, Punct, TokenStream, TokenTree}; +use std::iter::Peekable; + +use proc_macro2::{Delimiter, Group, Ident, Punct, TokenStream, TokenTree}; +use syn::spanned::Spanned; /// The "formality spec" guides parsing and serialization. /// @@ -33,6 +36,21 @@ pub enum FieldMode { /// $x -- just parse `x` Single, + /// $ -- `x` is a `Vec`, parse `` + /// $[x] -- `x` is a `Vec`, parse `[E0,...,En]` + /// $(x) -- `x` is a `Vec`, parse `(E0,...,En)` + /// $ -- `x` is a `Vec`, parse `` or empty list + /// $[?x] -- `x` is a `Vec`, parse `[E0,...,En]` or empty list + /// $(?x) -- `x` is a `Vec`, parse `(E0,...,En)` or empty list + /// + /// If the next op is a fixed character, stop parsing when we see that. + /// Otherwise parse as many we can greedily. + Delimited { + open: char, + optional: bool, + close: char, + }, + /// $*x -- `x` is a `Vec`, parse multiple `E` /// /// If the next op is a fixed character, stop parsing when we see that. @@ -70,18 +88,13 @@ fn token_stream_to_symbols( while let Some(token) = tokens.next() { match token { proc_macro2::TokenTree::Group(v) => { - let (open_text, close_text) = match v.delimiter() { - proc_macro2::Delimiter::Parenthesis => (Some('('), Some(')')), - proc_macro2::Delimiter::Brace => (Some('{'), Some('}')), - proc_macro2::Delimiter::Bracket => (Some('['), Some(']')), - proc_macro2::Delimiter::None => (None, None), - }; - - if let Some(ch) = open_text { + let open_close = open_close(&v); + + if let Some((ch, _)) = open_close { symbols.push(Delimeter { text: ch }); } token_stream_to_symbols(symbols, v.stream())?; - if let Some(ch) = close_text { + if let Some((_, ch)) = open_close { symbols.push(Delimeter { text: ch }); } } @@ -110,6 +123,7 @@ fn parse_variable_binding( dollar_token: Punct, tokens: &mut impl Iterator, ) -> syn::Result { + let dollar_token = &dollar_token; let mut tokens = tokens.peekable(); let Some(token) = tokens.peek() else { @@ -148,11 +162,74 @@ fn parse_variable_binding( parse_variable_binding_name(dollar_token, FieldMode::Optional, &mut tokens) } + // $ or $ + TokenTree::Punct(punct) if punct.as_char() == '<' => { + tokens.next(); + + // consume `x` or `?x` + let result = parse_delimited(dollar_token, '<', '>', &mut tokens)?; + + // we should see a `>` next + match tokens.next() { + Some(TokenTree::Punct(punct)) if punct.as_char() == '>' => Ok(result), + _ => error(dollar_token), + } + } + + // $(x) or $(?x) + // $[x] or $[?x] + // ${x} or ${?x} + TokenTree::Group(_) => { + let Some(TokenTree::Group(group)) = tokens.next() else { + unreachable!() + }; + let Some((open, close)) = open_close(&group) else { + return error(&group); + }; + + // consume `x` or `?x` + let mut group_tokens = group.stream().into_iter().peekable(); + let result = parse_delimited(dollar_token, open, close, &mut group_tokens)?; + + // there shouldn't be anything else in the token tree + if let Some(t) = group_tokens.next() { + return error(&t); + } + Ok(result) + } + _ => error(dollar_token), }; + fn parse_delimited( + dollar_token: &Punct, + open: char, + close: char, + tokens: &mut Peekable>, + ) -> syn::Result { + // Check for a `?` and consume it, if present. + let optional = match tokens.peek() { + Some(TokenTree::Punct(punct)) if punct.as_char() == '?' => { + tokens.next(); // drop `?` character + true + } + + _ => false, + }; + + parse_variable_binding_name( + dollar_token, + FieldMode::Delimited { + open, + optional, + close, + }, + tokens, + ) + } + fn parse_variable_binding_name( - dollar_token: Punct, + dollar_token: &Punct, mode: FieldMode, tokens: &mut impl Iterator, ) -> syn::Result { @@ -165,8 +242,17 @@ fn parse_variable_binding( Ok(FormalitySpecSymbol::Field { name, mode }) } - fn error(dollar_token: Punct) -> syn::Result { - let message = "expected field name or field mode (`,`, `*`)"; - Err(syn::Error::new(dollar_token.span(), message)) + fn error(at_token: &impl Spanned) -> syn::Result { + let message = "invalid field reference in grammar"; + Err(syn::Error::new(at_token.span(), message)) + } +} + +fn open_close(g: &Group) -> Option<(char, char)> { + match g.delimiter() { + proc_macro2::Delimiter::Parenthesis => Some(('(', ')')), + proc_macro2::Delimiter::Brace => Some(('{', '}')), + proc_macro2::Delimiter::Bracket => Some(('[', ']')), + proc_macro2::Delimiter::None => None, } } diff --git a/examples/formality-eg/grammar.rs b/examples/formality-eg/grammar.rs index fcaad4fc..a500d67f 100644 --- a/examples/formality-eg/grammar.rs +++ b/examples/formality-eg/grammar.rs @@ -30,7 +30,7 @@ pub enum Ty { Var(Variable), } -#[term($id < $,parameters >)] +#[term($id $)] pub struct StructTy { pub id: StructId, pub parameters: Vec, @@ -59,7 +59,7 @@ pub struct FnDecl { bound: Binder, } -#[term(($,fn_parameters) -> $return_ty { $body })] +#[term($(fn_parameters) -> $return_ty { $body })] pub struct FnBoundData { pub fn_parameters: Vec, pub return_ty: Ty, diff --git a/examples/formality-eg/grammar/test.rs b/examples/formality-eg/grammar/test.rs index 5a706e11..a93f0849 100644 --- a/examples/formality-eg/grammar/test.rs +++ b/examples/formality-eg/grammar/test.rs @@ -6,7 +6,7 @@ use super::{Expr, StructDecl, Ty}; #[test] fn test_struct_decl() { - let r: StructDecl = term("struct Point<> { x: integer, y: integer }"); + let r: StructDecl = term("struct Point { x: integer, y: integer }"); expect_test::expect![[r#" struct Point <> { x : integer, y : integer } "#]] @@ -17,7 +17,7 @@ fn test_struct_decl() { fn test_struct_ty() { let r: Ty = term("Point<>"); expect_test::expect![[r#" - Point < > + Point "#]] .assert_debug_eq(&r); } @@ -26,7 +26,7 @@ fn test_struct_ty() { fn test_vec_int_ty() { let r: Ty = term("Vec"); expect_test::expect![[r#" - Vec < integer > + Vec "#]] .assert_debug_eq(&r); } diff --git a/examples/formality-eg/main.rs b/examples/formality-eg/main.rs index 273dbd77..012159f6 100644 --- a/examples/formality-eg/main.rs +++ b/examples/formality-eg/main.rs @@ -13,6 +13,7 @@ formality_core::declare_language! { "fn", "let", "in", + "integer", ]; } } From 38f6feeb7705e486257440079da7d7db701b8190 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 4 Nov 2023 22:59:08 -0400 Subject: [PATCH 06/38] use `$<>` form in Rust grammar --- crates/formality-rust/src/grammar.rs | 8 ++++---- examples/formality-eg/grammar/test.rs | 10 +++++++++- tests/coherence_overlap.rs | 4 ++-- tests/ui/basic_where_clauses_fail.stderr | 4 ++-- .../CoreTrait_for_CoreStruct_in_Foo.stderr | 2 +- tests/ui/coherence_orphan/alias_to_unit.stderr | 2 +- tests/ui/coherence_orphan/mirror_CoreStruct.stderr | 2 +- .../neg_CoreTrait_for_CoreStruct_in_Foo.stderr | 2 +- tests/ui/coherence_orphan/uncovered_T.stderr | 2 +- .../coherence_overlap/T_where_Foo_not_u32_impls.stderr | 2 +- ...ot_assume_CoreStruct_does_not_impl_CoreTrait.stderr | 4 ++-- tests/ui/coherence_overlap/u32_T_impls.stderr | 4 ++-- .../ui/coherence_overlap/u32_T_where_T_Is_impls.stderr | 4 ++-- tests/ui/coherence_overlap/u32_not_u32_impls.stderr | 2 +- tests/ui/coherence_overlap/u32_u32_impls.stderr | 2 +- tests/ui/consts/generic_mismatch.stderr | 2 +- tests/ui/consts/mismatch.stderr | 2 +- tests/ui/hello_world_fail.stderr | 2 +- 18 files changed, 34 insertions(+), 26 deletions(-) diff --git a/crates/formality-rust/src/grammar.rs b/crates/formality-rust/src/grammar.rs index 424a3790..2bd8fb79 100644 --- a/crates/formality-rust/src/grammar.rs +++ b/crates/formality-rust/src/grammar.rs @@ -252,7 +252,7 @@ impl TraitImpl { } } -#[term($trait_id < $,trait_parameters > for $self_ty where $where_clauses { $*impl_items })] +#[term($trait_id $ for $self_ty where $where_clauses { $*impl_items })] pub struct TraitImplBoundData { pub trait_id: TraitId, pub self_ty: Ty, @@ -272,7 +272,7 @@ pub struct NegTraitImpl { pub binder: Binder, } -#[term(!$trait_id < $,trait_parameters > for $self_ty where $where_clauses { })] +#[term(!$trait_id $ for $self_ty where $where_clauses { })] pub struct NegTraitImplBoundData { pub trait_id: TraitId, pub self_ty: Ty, @@ -338,7 +338,7 @@ impl WhereClause { #[term] pub enum WhereClauseData { - #[grammar($v0 : $v1 < $,v2 >)] + #[grammar($v0 : $v1 $)] IsImplemented(Ty, TraitId, Vec), #[grammar($v0 => $v1)] @@ -367,7 +367,7 @@ impl WhereBound { #[term] pub enum WhereBoundData { - #[grammar($v0 < $,v1 >)] + #[grammar($v0 $)] IsImplemented(TraitId, Vec), #[grammar($v0)] diff --git a/examples/formality-eg/grammar/test.rs b/examples/formality-eg/grammar/test.rs index a93f0849..c3df7c4d 100644 --- a/examples/formality-eg/grammar/test.rs +++ b/examples/formality-eg/grammar/test.rs @@ -14,7 +14,7 @@ fn test_struct_decl() { } #[test] -fn test_struct_ty() { +fn test_struct_ty_empty_args() { let r: Ty = term("Point<>"); expect_test::expect![[r#" Point @@ -22,6 +22,14 @@ fn test_struct_ty() { .assert_debug_eq(&r); } +#[test] +fn test_struct_ty_no_args() { + let r: Ty = term("Point"); + expect_test::expect![[r#" + Point + "#]] + .assert_debug_eq(&r); +} #[test] fn test_vec_int_ty() { let r: Ty = term("Vec"); diff --git a/tests/coherence_overlap.rs b/tests/coherence_overlap.rs index aeb8f1bf..dbe68ced 100644 --- a/tests/coherence_overlap.rs +++ b/tests/coherence_overlap.rs @@ -53,7 +53,7 @@ fn test_overlap_normalize_alias_to_LocalType() { expect_test::expect![[r#" Err( - "impls may overlap:\nimpl LocalTrait < > for ^ty0_0 where [^ty0_0 : Iterator < >] { }\nimpl <> LocalTrait < > for ::T where [] { }", + "impls may overlap:\nimpl LocalTrait for ^ty0_0 where [^ty0_0 : Iterator] { }\nimpl <> LocalTrait for ::T where [] { }", ) "#]] .assert_debug_eq(&test_program_ok(&gen_program( @@ -114,7 +114,7 @@ fn test_overlap_alias_not_normalizable() { expect_test::expect![[r#" Err( - "impls may overlap:\nimpl LocalTrait < > for ^ty0_0 where [^ty0_0 : Iterator < >] { }\nimpl LocalTrait < > for <^ty0_0 as Mirror>::T where [^ty0_0 : Mirror < >] { }", + "impls may overlap:\nimpl LocalTrait for ^ty0_0 where [^ty0_0 : Iterator] { }\nimpl LocalTrait for <^ty0_0 as Mirror>::T where [^ty0_0 : Mirror] { }", ) "#]] // FIXME .assert_debug_eq(&test_program_ok(&gen_program( diff --git a/tests/ui/basic_where_clauses_fail.stderr b/tests/ui/basic_where_clauses_fail.stderr index da29e03a..a55bc4da 100644 --- a/tests/ui/basic_where_clauses_fail.stderr +++ b/tests/ui/basic_where_clauses_fail.stderr @@ -1,6 +1,6 @@ Error: check_trait(WellFormed) Caused by: - 0: prove_where_clause_well_formed(for u32 : A < ^ty0_0 >) - 1: prove_where_clause_well_formed(u32 : A < !ty_2 >) + 0: prove_where_clause_well_formed(for u32 : A <^ty0_0>) + 1: prove_where_clause_well_formed(u32 : A ) 2: failed to prove {@ WellFormedTraitRef(A(u32, !ty_2))} given {for A(u32, ^ty0_0)}, got {} diff --git a/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.stderr b/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.stderr index 3ef3c848..dd909bdf 100644 --- a/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.stderr +++ b/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.stderr @@ -1,4 +1,4 @@ -Error: orphan_check(impl <> CoreTrait < > for CoreStruct where [] { }) +Error: orphan_check(impl <> CoreTrait for CoreStruct where [] { }) Caused by: failed to prove {@ IsLocal(CoreTrait(CoreStruct))} given {}, got {} diff --git a/tests/ui/coherence_orphan/alias_to_unit.stderr b/tests/ui/coherence_orphan/alias_to_unit.stderr index cbb363b8..c56f163c 100644 --- a/tests/ui/coherence_orphan/alias_to_unit.stderr +++ b/tests/ui/coherence_orphan/alias_to_unit.stderr @@ -1,4 +1,4 @@ -Error: orphan_check(impl <> CoreTrait < > for ::Assoc where [] { }) +Error: orphan_check(impl <> CoreTrait for ::Assoc where [] { }) Caused by: failed to prove {@ IsLocal(CoreTrait(::Assoc))} given {}, got {} diff --git a/tests/ui/coherence_orphan/mirror_CoreStruct.stderr b/tests/ui/coherence_orphan/mirror_CoreStruct.stderr index 0450e2d9..ba994524 100644 --- a/tests/ui/coherence_orphan/mirror_CoreStruct.stderr +++ b/tests/ui/coherence_orphan/mirror_CoreStruct.stderr @@ -1,4 +1,4 @@ -Error: orphan_check(impl <> CoreTrait < > for ::Assoc where [] { }) +Error: orphan_check(impl <> CoreTrait for ::Assoc where [] { }) Caused by: failed to prove {@ IsLocal(CoreTrait(::Assoc))} given {}, got {} diff --git a/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.stderr b/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.stderr index 081e8613..2e99b68c 100644 --- a/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.stderr +++ b/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.stderr @@ -1,4 +1,4 @@ -Error: orphan_check_neg(impl <> ! CoreTrait < > for CoreStruct where [] {}) +Error: orphan_check_neg(impl <> ! CoreTrait for CoreStruct where [] {}) Caused by: failed to prove {@ IsLocal(CoreTrait(CoreStruct))} given {}, got {} diff --git a/tests/ui/coherence_orphan/uncovered_T.stderr b/tests/ui/coherence_orphan/uncovered_T.stderr index 35cfc0fc..b9b68e73 100644 --- a/tests/ui/coherence_orphan/uncovered_T.stderr +++ b/tests/ui/coherence_orphan/uncovered_T.stderr @@ -1,4 +1,4 @@ -Error: orphan_check(impl CoreTrait < FooStruct > for ^ty0_0 where [] { }) +Error: orphan_check(impl CoreTrait for ^ty0_0 where [] { }) Caused by: failed to prove {@ IsLocal(CoreTrait(!ty_1, FooStruct))} given {}, got {} diff --git a/tests/ui/coherence_overlap/T_where_Foo_not_u32_impls.stderr b/tests/ui/coherence_overlap/T_where_Foo_not_u32_impls.stderr index ecbbe8ee..c39b8a81 100644 --- a/tests/ui/coherence_overlap/T_where_Foo_not_u32_impls.stderr +++ b/tests/ui/coherence_overlap/T_where_Foo_not_u32_impls.stderr @@ -1,4 +1,4 @@ -Error: check_trait_impl(impl Foo < > for ^ty0_0 where [^ty0_0 : Foo < >] { }) +Error: check_trait_impl(impl Foo for ^ty0_0 where [^ty0_0 : Foo] { }) Caused by: failed to disprove diff --git a/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.stderr b/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.stderr index 280dfd6b..069ba29e 100644 --- a/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.stderr +++ b/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.stderr @@ -1,3 +1,3 @@ Error: impls may overlap: -impl FooTrait < > for ^ty0_0 where [^ty0_0 : CoreTrait < >] { } -impl <> FooTrait < > for CoreStruct where [] { } +impl FooTrait for ^ty0_0 where [^ty0_0 : CoreTrait] { } +impl <> FooTrait for CoreStruct where [] { } diff --git a/tests/ui/coherence_overlap/u32_T_impls.stderr b/tests/ui/coherence_overlap/u32_T_impls.stderr index 83ac7c7c..54aeff4d 100644 --- a/tests/ui/coherence_overlap/u32_T_impls.stderr +++ b/tests/ui/coherence_overlap/u32_T_impls.stderr @@ -1,3 +1,3 @@ Error: impls may overlap: -impl <> Foo < > for u32 where [] { } -impl Foo < > for ^ty0_0 where [] { } +impl <> Foo for u32 where [] { } +impl Foo for ^ty0_0 where [] { } diff --git a/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.stderr b/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.stderr index b4bbfe86..b4d050c1 100644 --- a/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.stderr +++ b/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.stderr @@ -1,3 +1,3 @@ Error: impls may overlap: -impl <> Foo < > for u32 where [] { } -impl Foo < > for ^ty0_0 where [^ty0_0 : Is < >] { } +impl <> Foo for u32 where [] { } +impl Foo for ^ty0_0 where [^ty0_0 : Is] { } diff --git a/tests/ui/coherence_overlap/u32_not_u32_impls.stderr b/tests/ui/coherence_overlap/u32_not_u32_impls.stderr index 65398442..4f2ced4a 100644 --- a/tests/ui/coherence_overlap/u32_not_u32_impls.stderr +++ b/tests/ui/coherence_overlap/u32_not_u32_impls.stderr @@ -1,4 +1,4 @@ -Error: check_trait_impl(impl <> Foo < > for u32 where [] { }) +Error: check_trait_impl(impl <> Foo for u32 where [] { }) Caused by: failed to disprove diff --git a/tests/ui/coherence_overlap/u32_u32_impls.stderr b/tests/ui/coherence_overlap/u32_u32_impls.stderr index fb0aa5a5..47e40bc9 100644 --- a/tests/ui/coherence_overlap/u32_u32_impls.stderr +++ b/tests/ui/coherence_overlap/u32_u32_impls.stderr @@ -1 +1 @@ -Error: duplicate impl in current crate: impl <> Foo < > for u32 where [] { } +Error: duplicate impl in current crate: impl <> Foo for u32 where [] { } diff --git a/tests/ui/consts/generic_mismatch.stderr b/tests/ui/consts/generic_mismatch.stderr index 1b15792b..187103b2 100644 --- a/tests/ui/consts/generic_mismatch.stderr +++ b/tests/ui/consts/generic_mismatch.stderr @@ -1,4 +1,4 @@ -Error: check_trait_impl(impl Foo < const ^const0_0 > for u32 where [type_of_const ^const0_0 is u32] { }) +Error: check_trait_impl(impl Foo for u32 where [type_of_const ^const0_0 is u32] { }) Caused by: failed to prove {Foo(u32, const !const_1)} given {@ ConstHasType(!const_1 , u32)}, got {} diff --git a/tests/ui/consts/mismatch.stderr b/tests/ui/consts/mismatch.stderr index f1bf5189..785231bd 100644 --- a/tests/ui/consts/mismatch.stderr +++ b/tests/ui/consts/mismatch.stderr @@ -1,4 +1,4 @@ -Error: check_trait_impl(impl <> Foo < const value(42, u32) > for u32 where [] { }) +Error: check_trait_impl(impl <> Foo for u32 where [] { }) Caused by: failed to prove {Foo(u32, const value(42, u32))} given {}, got {} diff --git a/tests/ui/hello_world_fail.stderr b/tests/ui/hello_world_fail.stderr index 93081ae3..1d9f5654 100644 --- a/tests/ui/hello_world_fail.stderr +++ b/tests/ui/hello_world_fail.stderr @@ -1,5 +1,5 @@ Error: check_trait(Foo) Caused by: - 0: prove_where_clause_well_formed(!ty_2 : Bar < !ty_1 >) + 0: prove_where_clause_well_formed(!ty_2 : Bar ) 1: failed to prove {@ WellFormedTraitRef(Bar(!ty_2, !ty_1))} given {Bar(!ty_2, !ty_1)}, got {} From 01d59ddd55312eb673d7ee93c6a1bd145b7e01b0 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 4 Nov 2023 23:00:28 -0400 Subject: [PATCH 07/38] don't print `<>` if no variables are bound --- crates/formality-core/src/binder.rs | 14 ++++++++------ examples/formality-eg/grammar/test.rs | 2 +- tests/coherence_overlap.rs | 2 +- .../CoreTrait_for_CoreStruct_in_Foo.stderr | 2 +- tests/ui/coherence_orphan/alias_to_unit.stderr | 2 +- tests/ui/coherence_orphan/mirror_CoreStruct.stderr | 2 +- .../neg_CoreTrait_for_CoreStruct_in_Foo.stderr | 2 +- ...ssume_CoreStruct_does_not_impl_CoreTrait.stderr | 2 +- tests/ui/coherence_overlap/u32_T_impls.stderr | 2 +- .../u32_T_where_T_Is_impls.stderr | 2 +- .../ui/coherence_overlap/u32_not_u32_impls.stderr | 2 +- tests/ui/coherence_overlap/u32_u32_impls.stderr | 2 +- tests/ui/consts/mismatch.stderr | 2 +- 13 files changed, 20 insertions(+), 18 deletions(-) diff --git a/crates/formality-core/src/binder.rs b/crates/formality-core/src/binder.rs index 94f10efe..fbb589ec 100644 --- a/crates/formality-core/src/binder.rs +++ b/crates/formality-core/src/binder.rs @@ -261,14 +261,16 @@ where T: std::fmt::Debug, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "<")?; - for (kind, i) in self.kinds.iter().zip(0..) { - if i > 0 { - write!(f, ", ")?; + if !self.kinds.is_empty() { + write!(f, "<")?; + for (kind, i) in self.kinds.iter().zip(0..) { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{:?}", kind)?; } - write!(f, "{:?}", kind)?; + write!(f, "> ")?; } - write!(f, "> ")?; write!(f, "{:?}", &self.term)?; Ok(()) } diff --git a/examples/formality-eg/grammar/test.rs b/examples/formality-eg/grammar/test.rs index c3df7c4d..e8f2e895 100644 --- a/examples/formality-eg/grammar/test.rs +++ b/examples/formality-eg/grammar/test.rs @@ -8,7 +8,7 @@ use super::{Expr, StructDecl, Ty}; fn test_struct_decl() { let r: StructDecl = term("struct Point { x: integer, y: integer }"); expect_test::expect![[r#" - struct Point <> { x : integer, y : integer } + struct Point { x : integer, y : integer } "#]] .assert_debug_eq(&r); } diff --git a/tests/coherence_overlap.rs b/tests/coherence_overlap.rs index dbe68ced..114b4a62 100644 --- a/tests/coherence_overlap.rs +++ b/tests/coherence_overlap.rs @@ -53,7 +53,7 @@ fn test_overlap_normalize_alias_to_LocalType() { expect_test::expect![[r#" Err( - "impls may overlap:\nimpl LocalTrait for ^ty0_0 where [^ty0_0 : Iterator] { }\nimpl <> LocalTrait for ::T where [] { }", + "impls may overlap:\nimpl LocalTrait for ^ty0_0 where [^ty0_0 : Iterator] { }\nimpl LocalTrait for ::T where [] { }", ) "#]] .assert_debug_eq(&test_program_ok(&gen_program( diff --git a/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.stderr b/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.stderr index dd909bdf..4261c067 100644 --- a/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.stderr +++ b/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.stderr @@ -1,4 +1,4 @@ -Error: orphan_check(impl <> CoreTrait for CoreStruct where [] { }) +Error: orphan_check(impl CoreTrait for CoreStruct where [] { }) Caused by: failed to prove {@ IsLocal(CoreTrait(CoreStruct))} given {}, got {} diff --git a/tests/ui/coherence_orphan/alias_to_unit.stderr b/tests/ui/coherence_orphan/alias_to_unit.stderr index c56f163c..6dd0cc6f 100644 --- a/tests/ui/coherence_orphan/alias_to_unit.stderr +++ b/tests/ui/coherence_orphan/alias_to_unit.stderr @@ -1,4 +1,4 @@ -Error: orphan_check(impl <> CoreTrait for ::Assoc where [] { }) +Error: orphan_check(impl CoreTrait for ::Assoc where [] { }) Caused by: failed to prove {@ IsLocal(CoreTrait(::Assoc))} given {}, got {} diff --git a/tests/ui/coherence_orphan/mirror_CoreStruct.stderr b/tests/ui/coherence_orphan/mirror_CoreStruct.stderr index ba994524..42288056 100644 --- a/tests/ui/coherence_orphan/mirror_CoreStruct.stderr +++ b/tests/ui/coherence_orphan/mirror_CoreStruct.stderr @@ -1,4 +1,4 @@ -Error: orphan_check(impl <> CoreTrait for ::Assoc where [] { }) +Error: orphan_check(impl CoreTrait for ::Assoc where [] { }) Caused by: failed to prove {@ IsLocal(CoreTrait(::Assoc))} given {}, got {} diff --git a/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.stderr b/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.stderr index 2e99b68c..0136c2a5 100644 --- a/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.stderr +++ b/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.stderr @@ -1,4 +1,4 @@ -Error: orphan_check_neg(impl <> ! CoreTrait for CoreStruct where [] {}) +Error: orphan_check_neg(impl ! CoreTrait for CoreStruct where [] {}) Caused by: failed to prove {@ IsLocal(CoreTrait(CoreStruct))} given {}, got {} diff --git a/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.stderr b/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.stderr index 069ba29e..0c464196 100644 --- a/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.stderr +++ b/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.stderr @@ -1,3 +1,3 @@ Error: impls may overlap: impl FooTrait for ^ty0_0 where [^ty0_0 : CoreTrait] { } -impl <> FooTrait for CoreStruct where [] { } +impl FooTrait for CoreStruct where [] { } diff --git a/tests/ui/coherence_overlap/u32_T_impls.stderr b/tests/ui/coherence_overlap/u32_T_impls.stderr index 54aeff4d..8d63fc96 100644 --- a/tests/ui/coherence_overlap/u32_T_impls.stderr +++ b/tests/ui/coherence_overlap/u32_T_impls.stderr @@ -1,3 +1,3 @@ Error: impls may overlap: -impl <> Foo for u32 where [] { } +impl Foo for u32 where [] { } impl Foo for ^ty0_0 where [] { } diff --git a/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.stderr b/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.stderr index b4d050c1..2d6e18de 100644 --- a/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.stderr +++ b/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.stderr @@ -1,3 +1,3 @@ Error: impls may overlap: -impl <> Foo for u32 where [] { } +impl Foo for u32 where [] { } impl Foo for ^ty0_0 where [^ty0_0 : Is] { } diff --git a/tests/ui/coherence_overlap/u32_not_u32_impls.stderr b/tests/ui/coherence_overlap/u32_not_u32_impls.stderr index 4f2ced4a..b0eed6f5 100644 --- a/tests/ui/coherence_overlap/u32_not_u32_impls.stderr +++ b/tests/ui/coherence_overlap/u32_not_u32_impls.stderr @@ -1,4 +1,4 @@ -Error: check_trait_impl(impl <> Foo for u32 where [] { }) +Error: check_trait_impl(impl Foo for u32 where [] { }) Caused by: failed to disprove diff --git a/tests/ui/coherence_overlap/u32_u32_impls.stderr b/tests/ui/coherence_overlap/u32_u32_impls.stderr index 47e40bc9..f1418eee 100644 --- a/tests/ui/coherence_overlap/u32_u32_impls.stderr +++ b/tests/ui/coherence_overlap/u32_u32_impls.stderr @@ -1 +1 @@ -Error: duplicate impl in current crate: impl <> Foo for u32 where [] { } +Error: duplicate impl in current crate: impl Foo for u32 where [] { } diff --git a/tests/ui/consts/mismatch.stderr b/tests/ui/consts/mismatch.stderr index 785231bd..5f917490 100644 --- a/tests/ui/consts/mismatch.stderr +++ b/tests/ui/consts/mismatch.stderr @@ -1,4 +1,4 @@ -Error: check_trait_impl(impl <> Foo for u32 where [] { }) +Error: check_trait_impl(impl Foo for u32 where [] { }) Caused by: failed to prove {Foo(u32, const value(42, u32))} given {}, got {} From f5d4a2c26f3e261160e38bfb49e96fbf9b42e58e Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 4 Nov 2023 23:02:07 -0400 Subject: [PATCH 08/38] use language's binder character --- crates/formality-core/src/binder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/formality-core/src/binder.rs b/crates/formality-core/src/binder.rs index fbb589ec..525dea75 100644 --- a/crates/formality-core/src/binder.rs +++ b/crates/formality-core/src/binder.rs @@ -262,14 +262,14 @@ where { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if !self.kinds.is_empty() { - write!(f, "<")?; + write!(f, "{}", L::BINDING_OPEN)?; for (kind, i) in self.kinds.iter().zip(0..) { if i > 0 { write!(f, ", ")?; } write!(f, "{:?}", kind)?; } - write!(f, "> ")?; + write!(f, "{} ", L::BINDING_CLOSE)?; } write!(f, "{:?}", &self.term)?; Ok(()) From 60b2cae919976e8f50f438d9491a2c6b01ab719c Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 4 Nov 2023 23:02:15 -0400 Subject: [PATCH 09/38] remove unused imports --- crates/formality-macros/src/spec.rs | 2 +- examples/formality-eg/grammar/test.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/formality-macros/src/spec.rs b/crates/formality-macros/src/spec.rs index 555d3673..fa589b78 100644 --- a/crates/formality-macros/src/spec.rs +++ b/crates/formality-macros/src/spec.rs @@ -1,6 +1,6 @@ use std::iter::Peekable; -use proc_macro2::{Delimiter, Group, Ident, Punct, TokenStream, TokenTree}; +use proc_macro2::{Group, Ident, Punct, TokenStream, TokenTree}; use syn::spanned::Spanned; /// The "formality spec" guides parsing and serialization. diff --git a/examples/formality-eg/grammar/test.rs b/examples/formality-eg/grammar/test.rs index e8f2e895..59e9c225 100644 --- a/examples/formality-eg/grammar/test.rs +++ b/examples/formality-eg/grammar/test.rs @@ -2,7 +2,7 @@ use formality_core::test; use crate::eg::term; -use super::{Expr, StructDecl, Ty}; +use super::{StructDecl, Ty}; #[test] fn test_struct_decl() { From 4a5e917193682f031e2631018dfc3950203f6e0c Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 4 Nov 2023 23:04:26 -0400 Subject: [PATCH 10/38] use `$()` for parameter listing --- crates/formality-rust/src/grammar.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/formality-rust/src/grammar.rs b/crates/formality-rust/src/grammar.rs index 2bd8fb79..043e55b9 100644 --- a/crates/formality-rust/src/grammar.rs +++ b/crates/formality-rust/src/grammar.rs @@ -199,7 +199,7 @@ pub struct Fn { pub binder: Binder, } -#[term(($,input_tys) -> $output_ty where $where_clauses $body)] +#[term($(input_tys) -> $output_ty where $where_clauses $body)] pub struct FnBoundData { pub input_tys: Vec, pub output_ty: Ty, From 8fe05f90df79ec2ba0494e31cd20f776f22f06d1 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 4 Nov 2023 23:11:46 -0400 Subject: [PATCH 11/38] rename from Delimited to DelimitedVec --- crates/formality-macros/src/debug.rs | 2 +- crates/formality-macros/src/parse.rs | 2 +- crates/formality-macros/src/spec.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/formality-macros/src/debug.rs b/crates/formality-macros/src/debug.rs index c96825bf..d3400447 100644 --- a/crates/formality-macros/src/debug.rs +++ b/crates/formality-macros/src/debug.rs @@ -197,7 +197,7 @@ fn debug_variant_with_attr( spec::FormalitySpecSymbol::Field { name, mode: - FieldMode::Delimited { + FieldMode::DelimitedVec { open, optional, close, diff --git a/crates/formality-macros/src/parse.rs b/crates/formality-macros/src/parse.rs index 95de1297..767e4a95 100644 --- a/crates/formality-macros/src/parse.rs +++ b/crates/formality-macros/src/parse.rs @@ -182,7 +182,7 @@ fn parse_variant_with_attr( spec::FormalitySpecSymbol::Field { name, mode: - FieldMode::Delimited { + FieldMode::DelimitedVec { open, optional, close, diff --git a/crates/formality-macros/src/spec.rs b/crates/formality-macros/src/spec.rs index fa589b78..8393770c 100644 --- a/crates/formality-macros/src/spec.rs +++ b/crates/formality-macros/src/spec.rs @@ -45,7 +45,7 @@ pub enum FieldMode { /// /// If the next op is a fixed character, stop parsing when we see that. /// Otherwise parse as many we can greedily. - Delimited { + DelimitedVec { open: char, optional: bool, close: char, @@ -219,7 +219,7 @@ fn parse_variable_binding( parse_variable_binding_name( dollar_token, - FieldMode::Delimited { + FieldMode::DelimitedVec { open, optional, close, From f7dbc28ee2ba9dbf04bd9742d83aa4f053702b5d Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 4 Nov 2023 23:20:24 -0400 Subject: [PATCH 12/38] refactor parsing codegen We are going to want to make recursive modes. --- crates/formality-macros/src/parse.rs | 101 +++++++++++++-------------- 1 file changed, 48 insertions(+), 53 deletions(-) diff --git a/crates/formality-macros/src/parse.rs b/crates/formality-macros/src/parse.rs index 767e4a95..c889eaab 100644 --- a/crates/formality-macros/src/parse.rs +++ b/crates/formality-macros/src/parse.rs @@ -1,7 +1,7 @@ extern crate proc_macro; use convert_case::{Case, Casing}; -use proc_macro2::{Ident, Literal, TokenStream}; +use proc_macro2::{Ident, Literal, Span, TokenStream}; use quote::{quote, quote_spanned}; use syn::{spanned::Spanned, Attribute}; use synstructure::BindingInfo; @@ -148,60 +148,11 @@ fn parse_variant_with_attr( for symbol in &spec.symbols { stream.extend(match symbol { - spec::FormalitySpecSymbol::Field { - name, - mode: FieldMode::Single, - } => { + spec::FormalitySpecSymbol::Field { name, mode } => { + let initializer = parse_field_mode(name.span(), mode); quote_spanned! { name.span() => - let #name = __p.nonterminal()?; - } - } - - spec::FormalitySpecSymbol::Field { - name, - mode: FieldMode::Optional, - } => { - quote_spanned! { - name.span() => - let #name = __p.opt_nonterminal()?; - let #name = #name.unwrap_or_default(); - } - } - - spec::FormalitySpecSymbol::Field { - name, - mode: FieldMode::Many, - } => { - quote_spanned! { - name.span() => - let #name = __p.many_nonterminal()?; - } - } - - spec::FormalitySpecSymbol::Field { - name, - mode: - FieldMode::DelimitedVec { - open, - optional, - close, - }, - } => { - let open = Literal::character(*open); - let close = Literal::character(*close); - quote_spanned! { - name.span() => - let #name = __p.delimited_nonterminal(#open, #optional, #close)?; - } - } - spec::FormalitySpecSymbol::Field { - name, - mode: FieldMode::Comma, - } => { - quote_spanned! { - name.span() => - let #name = __p.comma_nonterminal()?; + let #name = #initializer; } } @@ -238,6 +189,50 @@ fn parse_variant_with_attr( Ok(stream) } +fn parse_field_mode(span: Span, mode: &FieldMode) -> TokenStream { + match mode { + FieldMode::Single => { + quote_spanned! { + span => + __p.nonterminal()? + } + } + + FieldMode::Optional => { + quote_spanned! { + span => + __p.opt_nonterminal()?.unwrap_or_default() + } + } + + FieldMode::Many => { + quote_spanned! { + span => + __p.many_nonterminal()? + } + } + + FieldMode::DelimitedVec { + open, + optional, + close, + } => { + let open = Literal::character(*open); + let close = Literal::character(*close); + quote_spanned! { + span => + __p.delimited_nonterminal(#open, #optional, #close)? + } + } + FieldMode::Comma => { + quote_spanned! { + span => + __p.comma_nonterminal()? + } + } + } +} + fn get_grammar_attr(attrs: &[Attribute]) -> Option> { let attr = attrs.iter().find(|a| a.path().is_ident("grammar"))?; Some(attr.parse_args()) From 5136925d2a6cc93581ac03bb53673a3f6fe82899 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 5 Nov 2023 05:06:41 -0500 Subject: [PATCH 13/38] refactor debug codegen this will better support the new gated parsing mode --- crates/formality-macros/src/debug.rs | 125 ++++++++++++--------------- 1 file changed, 57 insertions(+), 68 deletions(-) diff --git a/crates/formality-macros/src/debug.rs b/crates/formality-macros/src/debug.rs index d3400447..018faae5 100644 --- a/crates/formality-macros/src/debug.rs +++ b/crates/formality-macros/src/debug.rs @@ -153,74 +153,7 @@ fn debug_variant_with_attr( } stream.extend(match op { - spec::FormalitySpecSymbol::Field { - name, - mode: FieldMode::Single | FieldMode::Optional, - } => { - quote_spanned! { - name.span() => - write!(fmt, "{}", sep)?; - write!(fmt, "{:?}", #name)?; - sep = " "; - } - } - - spec::FormalitySpecSymbol::Field { - name, - mode: FieldMode::Many, - } => { - quote_spanned! { - name.span() => - for e in #name { - write!(fmt, "{}", sep)?; - write!(fmt, "{:?}", e)?; - sep = " "; - } - } - } - - spec::FormalitySpecSymbol::Field { - name, - mode: FieldMode::Comma, - } => { - quote_spanned! { - name.span() => - for e in #name { - write!(fmt, "{}", sep)?; - write!(fmt, "{:?}", e)?; - sep = ", "; - } - sep = " "; - } - } - - spec::FormalitySpecSymbol::Field { - name, - mode: - FieldMode::DelimitedVec { - open, - optional, - close, - }, - } => { - let open = Literal::character(*open); - let close = Literal::character(*close); - quote_spanned! { - name.span() => - if !#optional || !#name.is_empty() { - write!(fmt, "{}", sep)?; - write!(fmt, "{}", #open)?; - sep = ""; - for e in #name { - write!(fmt, "{}", sep)?; - write!(fmt, "{:?}", e)?; - sep = ", "; - } - write!(fmt, "{}", #close)?; - sep = " "; - } - } - } + spec::FormalitySpecSymbol::Field { name, mode } => debug_field_with_mode(name, mode), spec::FormalitySpecSymbol::Keyword { ident } => { let literal = as_literal(ident); @@ -270,6 +203,62 @@ fn debug_variant_with_attr( stream } +fn debug_field_with_mode(name: &Ident, mode: &FieldMode) -> TokenStream { + match mode { + FieldMode::Single | FieldMode::Optional => { + quote_spanned! { name.span() => + write!(fmt, "{}", sep)?; + write!(fmt, "{:?}", #name)?; + sep = " "; + } + } + + FieldMode::Many => { + quote_spanned! { name.span() => + for e in #name { + write!(fmt, "{}", sep)?; + write!(fmt, "{:?}", e)?; + sep = " "; + } + } + } + + FieldMode::Comma => { + quote_spanned! { name.span() => + for e in #name { + write!(fmt, "{}", sep)?; + write!(fmt, "{:?}", e)?; + sep = ", "; + } + sep = " "; + } + } + + FieldMode::DelimitedVec { + open, + optional, + close, + } => { + let open = Literal::character(*open); + let close = Literal::character(*close); + quote_spanned! { name.span() => + if !#optional || !#name.is_empty() { + write!(fmt, "{}", sep)?; + write!(fmt, "{}", #open)?; + sep = ""; + for e in #name { + write!(fmt, "{}", sep)?; + write!(fmt, "{:?}", e)?; + sep = ", "; + } + write!(fmt, "{}", #close)?; + sep = " "; + } + } + } + } +} + fn get_grammar_attr(attrs: &[Attribute]) -> Option> { let attr = attrs.iter().find(|a| a.path().is_ident("grammar"))?; Some(attr.parse_args()) From c30ff77b5b0dd7a6e5bfd20683814dd3783fc263 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 5 Nov 2023 05:37:28 -0500 Subject: [PATCH 14/38] use guarded bindings for optional where-clauses Woohoo! --- crates/formality-core/src/lib.rs | 1 + crates/formality-core/src/util.rs | 9 ++ crates/formality-macros/src/debug.rs | 13 +++ crates/formality-macros/src/parse.rs | 12 +++ crates/formality-macros/src/spec.rs | 93 ++++++++++++++++--- crates/formality-prove/src/decls.rs | 12 +-- crates/formality-rust/src/grammar.rs | 16 ++-- crates/formality-rust/src/test.rs | 12 +-- crates/formality-types/src/grammar/wc.rs | 7 +- fixme_tests/basic--impl_Debug_for_i32.rs | 4 +- fixme_tests/basic--supertraits-hr.rs | 4 +- fixme_tests/basic--supertraits.rs | 8 +- fixme_tests/coinductive.rs | 56 +++++------ fixme_tests/impl-vs-trait-fn.rs | 34 +++---- fixme_tests/wf-impl--supertrait-required.rs | 16 ++-- tests/associated_type_normalization.rs | 8 +- tests/coherence_overlap.rs | 44 ++++----- tests/projection.rs | 46 ++++----- ...basic_where_clauses_fail.\360\237\224\254" | 6 +- ...basic_where_clauses_pass.\360\237\224\254" | 8 +- .../CoreTrait_for_CoreStruct_in_Foo.stderr | 2 +- ...it_for_CoreStruct_in_Foo.\360\237\224\254" | 6 +- .../ui/coherence_orphan/alias_to_unit.stderr | 2 +- .../alias_to_unit.\360\237\224\254" | 14 +-- .../covered_VecT.\360\237\224\254" | 8 +- .../coherence_orphan/mirror_CoreStruct.stderr | 2 +- .../mirror_CoreStruct.\360\237\224\254" | 14 +-- .../mirror_FooStruct.\360\237\224\254" | 14 +-- ...neg_CoreTrait_for_CoreStruct_in_Foo.stderr | 2 +- ...it_for_CoreStruct_in_Foo.\360\237\224\254" | 6 +- tests/ui/coherence_orphan/uncovered_T.stderr | 2 +- .../uncovered_T.\360\237\224\254" | 6 +- .../T_where_Foo_not_u32_impls.stderr | 2 +- ..._where_Foo_not_u32_impls.\360\237\224\254" | 6 +- ..._CoreStruct_does_not_impl_CoreTrait.stderr | 4 +- ..._does_not_impl_CoreTrait.\360\237\224\254" | 10 +- ...truct_implies_no_overlap.\360\237\224\254" | 12 +-- tests/ui/coherence_overlap/u32_T_impls.stderr | 4 +- .../u32_T_impls.\360\237\224\254" | 6 +- .../u32_T_where_T_Is_impls.stderr | 4 +- .../u32_T_where_T_Is_impls.\360\237\224\254" | 10 +- .../u32_T_where_T_Not_impls.\360\237\224\254" | 8 +- .../u32_i32_impls.\360\237\224\254" | 6 +- .../u32_not_u32_impls.stderr | 2 +- .../u32_not_u32_impls.\360\237\224\254" | 6 +- .../ui/coherence_overlap/u32_u32_impls.stderr | 2 +- .../u32_u32_impls.\360\237\224\254" | 6 +- tests/ui/consts/generic_mismatch.stderr | 2 +- .../consts/generic_mismatch.\360\237\224\254" | 4 +- "tests/ui/consts/holds.\360\237\224\254" | 4 +- tests/ui/consts/mismatch.stderr | 2 +- "tests/ui/consts/mismatch.\360\237\224\254" | 4 +- .../multiple_type_of_const.\360\237\224\254" | 2 +- ...nsense_rigid_const_bound.\360\237\224\254" | 2 +- "tests/ui/consts/ok.\360\237\224\254" | 6 +- .../rigid_const_bound.\360\237\224\254" | 2 +- "tests/ui/fn/lifetime.\360\237\224\254" | 2 +- "tests/ui/fn/ok.\360\237\224\254" | 10 +- "tests/ui/hello_world.\360\237\224\254" | 12 +-- "tests/ui/hello_world_fail.\360\237\224\254" | 6 +- tests/ui/parser.stderr | 4 +- "tests/ui/parser.\360\237\224\254" | 2 +- 62 files changed, 368 insertions(+), 271 deletions(-) create mode 100644 crates/formality-core/src/util.rs diff --git a/crates/formality-core/src/lib.rs b/crates/formality-core/src/lib.rs index 63d416c7..1caba694 100644 --- a/crates/formality-core/src/lib.rs +++ b/crates/formality-core/src/lib.rs @@ -32,6 +32,7 @@ pub mod language; pub mod parse; pub mod substitution; pub mod term; +pub mod util; pub mod variable; pub mod visit; diff --git a/crates/formality-core/src/util.rs b/crates/formality-core/src/util.rs new file mode 100644 index 00000000..691505de --- /dev/null +++ b/crates/formality-core/src/util.rs @@ -0,0 +1,9 @@ +/// Returns true if `t` is the default value for `t`. +/// Used by the "derive" code for `Debug`. +pub fn is_default(t: &T) -> bool +where + T: Default + Eq, +{ + let default_value: T = Default::default(); + default_value == *t +} diff --git a/crates/formality-macros/src/debug.rs b/crates/formality-macros/src/debug.rs index 018faae5..779b6829 100644 --- a/crates/formality-macros/src/debug.rs +++ b/crates/formality-macros/src/debug.rs @@ -256,6 +256,19 @@ fn debug_field_with_mode(name: &Ident, mode: &FieldMode) -> TokenStream { } } } + + FieldMode::Guarded { guard, mode } => { + let guard = as_literal(guard); + let base = debug_field_with_mode(name, mode); + + quote_spanned! { name.span() => + if !::formality_core::util::is_default(#name) { + write!(fmt, "{}{}", sep, #guard)?; + sep = " "; + #base + } + } + } } } diff --git a/crates/formality-macros/src/parse.rs b/crates/formality-macros/src/parse.rs index c889eaab..7b0d4f61 100644 --- a/crates/formality-macros/src/parse.rs +++ b/crates/formality-macros/src/parse.rs @@ -230,6 +230,18 @@ fn parse_field_mode(span: Span, mode: &FieldMode) -> TokenStream { __p.comma_nonterminal()? } } + + FieldMode::Guarded { guard, mode } => { + let guard_keyword = as_literal(guard); + let initializer = parse_field_mode(span, mode); + quote_spanned! { + span => + match __p.expect_keyword(#guard_keyword) { + Ok(()) => #initializer, + Err(_) => Default::default(), + } + } + } } } diff --git a/crates/formality-macros/src/spec.rs b/crates/formality-macros/src/spec.rs index 8393770c..21549981 100644 --- a/crates/formality-macros/src/spec.rs +++ b/crates/formality-macros/src/spec.rs @@ -1,4 +1,4 @@ -use std::iter::Peekable; +use std::{iter::Peekable, sync::Arc}; use proc_macro2::{Group, Ident, Punct, TokenStream, TokenTree}; use syn::spanned::Spanned; @@ -36,6 +36,11 @@ pub enum FieldMode { /// $x -- just parse `x` Single, + Guarded { + guard: Ident, + mode: Arc, + }, + /// $ -- `x` is a `Vec`, parse `` /// $[x] -- `x` is a `Vec`, parse `[E0,...,En]` /// $(x) -- `x` is a `Vec`, parse `(E0,...,En)` @@ -100,7 +105,7 @@ fn token_stream_to_symbols( } proc_macro2::TokenTree::Ident(ident) => symbols.push(Keyword { ident }), proc_macro2::TokenTree::Punct(punct) => match punct.as_char() { - '$' => symbols.push(parse_variable_binding(punct, &mut tokens)?), + '$' => symbols.push(parse_variable_binding(&punct, &mut tokens)?), _ => symbols.push(Char { punct }), }, proc_macro2::TokenTree::Literal(_) => { @@ -120,14 +125,16 @@ fn token_stream_to_symbols( /// or we could also see a `$`, in which case user wrote `$$`, and we treat that as a single /// `$` sign. fn parse_variable_binding( - dollar_token: Punct, - tokens: &mut impl Iterator, + dollar_token: &Punct, + tokens: &mut dyn Iterator, ) -> syn::Result { - let dollar_token = &dollar_token; let mut tokens = tokens.peekable(); let Some(token) = tokens.peek() else { - return error(dollar_token); + return error( + dollar_token, + "incomplete field reference; use `$$` if you just want a dollar sign", + ); }; return match token { @@ -162,6 +169,12 @@ fn parse_variable_binding( parse_variable_binding_name(dollar_token, FieldMode::Optional, &mut tokens) } + // $:guard $x + TokenTree::Punct(punct) if punct.as_char() == ':' => { + let guard_token = tokens.next().unwrap(); + parse_guarded_variable_binding(dollar_token, guard_token, &mut tokens) + } + // $ or $ TokenTree::Punct(punct) if punct.as_char() == '<' => { tokens.next(); @@ -172,7 +185,7 @@ fn parse_variable_binding( // we should see a `>` next match tokens.next() { Some(TokenTree::Punct(punct)) if punct.as_char() == '>' => Ok(result), - _ => error(dollar_token), + _ => error(dollar_token, "expected a `>` to end this field reference"), } } @@ -184,7 +197,7 @@ fn parse_variable_binding( unreachable!() }; let Some((open, close)) = open_close(&group) else { - return error(&group); + return error(&group, "did not expect a spliced macro_rules group here"); }; // consume `x` or `?x` @@ -193,14 +206,65 @@ fn parse_variable_binding( // there shouldn't be anything else in the token tree if let Some(t) = group_tokens.next() { - return error(&t); + return error( + &t, + "extra characters in delimited field reference after field name", + ); } Ok(result) } - _ => error(dollar_token), + _ => error(dollar_token, "invalid field reference"), }; + fn parse_guarded_variable_binding( + dollar_token: &Punct, + guard_token: TokenTree, + tokens: &mut Peekable>, + ) -> syn::Result { + // The next token should be an identifier + let Some(TokenTree::Ident(guard_ident)) = tokens.next() else { + return error( + &guard_token, + "expected an identifier after a `:` in a field reference", + ); + }; + + // The next token should be a `$`, beginning another variable binding + let next_dollar_token = match tokens.next() { + Some(TokenTree::Punct(next_dollar_token)) if next_dollar_token.as_char() == '$' => { + next_dollar_token + } + + _ => { + return error( + &dollar_token, + "expected another `$` field reference to follow the `:` guard", + ); + } + }; + + // Then should come another field reference. + let FormalitySpecSymbol::Field { name, mode } = + parse_variable_binding(&next_dollar_token, tokens)? + else { + return error( + &next_dollar_token, + "`$:` must be followed by another field reference, not a `$$` literal", + ); + }; + + let guard_mode = FieldMode::Guarded { + guard: guard_ident, + mode: Arc::new(mode), + }; + + Ok(FormalitySpecSymbol::Field { + name: name, + mode: guard_mode, + }) + } + fn parse_delimited( dollar_token: &Punct, open: char, @@ -236,14 +300,17 @@ fn parse_variable_binding( // Extract the name of the field. let name = match tokens.next() { Some(TokenTree::Ident(name)) => name, - _ => return error(dollar_token), + _ => return error(dollar_token, "expected field name"), }; Ok(FormalitySpecSymbol::Field { name, mode }) } - fn error(at_token: &impl Spanned) -> syn::Result { - let message = "invalid field reference in grammar"; + fn error(at_token: &impl Spanned, message: impl ToString) -> syn::Result { + let mut message = message.to_string(); + if message.is_empty() { + message = "invalid field reference in grammar".into(); + } Err(syn::Error::new(at_token.span(), message)) } } diff --git a/crates/formality-prove/src/decls.rs b/crates/formality-prove/src/decls.rs index 814e9fa9..38307b36 100644 --- a/crates/formality-prove/src/decls.rs +++ b/crates/formality-prove/src/decls.rs @@ -111,7 +111,7 @@ pub struct ImplDecl { } /// Data bound under the generics from [`ImplDecl`][] -#[term($trait_ref where $where_clause)] +#[term($trait_ref $:where $where_clause)] pub struct ImplDeclBoundData { /// The trait ref that is implemented pub trait_ref: TraitRef, @@ -129,7 +129,7 @@ pub struct NegImplDecl { } /// Data bound under the impl generics for a negative impl -#[term(!$trait_ref where $where_clause)] +#[term(!$trait_ref $:where $where_clause)] pub struct NegImplDeclBoundData { pub trait_ref: TraitRef, pub where_clause: Wcs, @@ -209,7 +209,7 @@ pub struct TraitInvariantBoundData { } /// The "bound data" for a [`TraitDecl`][] -- i.e., what is covered by the forall. -#[term(where $where_clause)] +#[term($:where $where_clause)] pub struct TraitDeclBoundData { /// The where-clauses declared on the trait pub where_clause: Wcs, @@ -231,7 +231,7 @@ impl AliasEqDecl { } /// Data bound under the impl generics for a [`AliasEqDecl`][] -#[term($alias = $ty where $where_clause)] +#[term($alias = $ty $:where $where_clause)] pub struct AliasEqDeclBoundData { /// The alias that is equal pub alias: AliasTy, @@ -258,7 +258,7 @@ impl AliasBoundDecl { } } -#[term($alias : $ensures where $where_clause)] +#[term($alias : $ensures $:where $where_clause)] pub struct AliasBoundDeclBoundData { pub alias: AliasTy, // FIXME: this is currently encoded as something like ` [T: Foo]` where @@ -281,7 +281,7 @@ pub struct AdtDecl { } /// The "bound data" for a [`AdtDecl`][]. -#[term(where $where_clause)] +#[term($:where $where_clause)] pub struct AdtDeclBoundData { /// The where-clauses declared on the ADT, pub where_clause: Wcs, diff --git a/crates/formality-rust/src/grammar.rs b/crates/formality-rust/src/grammar.rs index 043e55b9..f36f52f9 100644 --- a/crates/formality-rust/src/grammar.rs +++ b/crates/formality-rust/src/grammar.rs @@ -96,7 +96,7 @@ impl Struct { } } -#[term(where $where_clauses { $,fields })] +#[term($:where $,where_clauses { $,fields })] pub struct StructBoundData { pub where_clauses: Vec, pub fields: Vec, @@ -148,7 +148,7 @@ pub struct Adt { pub binder: Binder, } -#[term(where $where_clauses { $,variants })] +#[term($:where $,where_clauses { $,variants })] pub struct AdtBoundData { pub where_clauses: Vec, pub variants: Vec, @@ -179,7 +179,7 @@ impl TraitBinder { } } -#[term(where $where_clauses { $*trait_items })] +#[term($:where $,where_clauses { $*trait_items })] pub struct TraitBoundData { pub where_clauses: Vec, pub trait_items: Vec, @@ -199,7 +199,7 @@ pub struct Fn { pub binder: Binder, } -#[term($(input_tys) -> $output_ty where $where_clauses $body)] +#[term($(input_tys) -> $output_ty $:where $,where_clauses $body)] pub struct FnBoundData { pub input_tys: Vec, pub output_ty: Ty, @@ -232,7 +232,7 @@ pub struct AssociatedTy { pub binder: Binder, } -#[term(: $ensures where $where_clauses)] +#[term(: $ensures $:where $,where_clauses)] pub struct AssociatedTyBoundData { /// So e.g. `type Item : [Sized]` would be encoded as ` (I: Sized)`. pub ensures: Vec, @@ -252,7 +252,7 @@ impl TraitImpl { } } -#[term($trait_id $ for $self_ty where $where_clauses { $*impl_items })] +#[term($trait_id $ for $self_ty $:where $,where_clauses { $*impl_items })] pub struct TraitImplBoundData { pub trait_id: TraitId, pub self_ty: Ty, @@ -272,7 +272,7 @@ pub struct NegTraitImpl { pub binder: Binder, } -#[term(!$trait_id $ for $self_ty where $where_clauses { })] +#[term(!$trait_id $ for $self_ty $:where $,where_clauses { })] pub struct NegTraitImplBoundData { pub trait_id: TraitId, pub self_ty: Ty, @@ -300,7 +300,7 @@ pub struct AssociatedTyValue { pub binder: Binder, } -#[term(= $ty where $where_clauses)] +#[term(= $ty $:where $,where_clauses)] pub struct AssociatedTyValueBoundData { pub where_clauses: Vec, pub ty: Ty, diff --git a/crates/formality-rust/src/test.rs b/crates/formality-rust/src/test.rs index eeb90a7f..e6960df8 100644 --- a/crates/formality-rust/src/test.rs +++ b/crates/formality-rust/src/test.rs @@ -11,7 +11,7 @@ fn test_parse_rust_like_trait_impl_syntax() { let r: Program = term( "[ crate core { - impl PartialEq for B where [] { + impl PartialEq for B { } } @@ -20,7 +20,7 @@ fn test_parse_rust_like_trait_impl_syntax() { // Note: the for etc are correctly accounted. expect_test::expect![[r#" - [crate core { impl PartialEq < ^ty0_0 > for ^ty0_1 where [] { } }] + [crate core { impl PartialEq < ^ty0_0 > for ^ty0_1 { } }] "#]] .assert_debug_eq(&r); } @@ -30,7 +30,7 @@ fn test_parse_rust_like_trait_syntax() { let r: Program = term( "[ crate core { - trait Foo where [A : Bar] { + trait Foo where A : Bar { } } @@ -39,7 +39,7 @@ fn test_parse_rust_like_trait_syntax() { // Note: two type parameters, and the 0th one is self: expect_test::expect![[r#" - [crate core { trait Foo where [^ty0_1 : Bar < ^ty0_0 >] { } }] + [crate core { trait Foo where ^ty0_1 : Bar < ^ty0_0 > { } }] "#]] .assert_debug_eq(&r); } @@ -49,7 +49,7 @@ fn test_parse_rust_like_struct_syntax() { let r: Program = term( "[ crate core { - struct Foo where [] { + struct Foo { a : A, } } @@ -58,7 +58,7 @@ fn test_parse_rust_like_struct_syntax() { // Note: two type parameters, and the 0th one is self: expect_test::expect![[r#" - [crate core { struct Foo where [] { a : ^ty0_0 } }] + [crate core { struct Foo { a : ^ty0_0 } }] "#]] .assert_debug_eq(&r); } diff --git a/crates/formality-types/src/grammar/wc.rs b/crates/formality-types/src/grammar/wc.rs index a3231c00..b3dae1b5 100644 --- a/crates/formality-types/src/grammar/wc.rs +++ b/crates/formality-types/src/grammar/wc.rs @@ -9,6 +9,7 @@ use crate::grammar::PR; use super::{Binder, BoundVar, Parameter, Predicate, Relation, TraitRef}; #[term($set)] +#[derive(Default)] pub struct Wcs { set: Set, } @@ -93,12 +94,6 @@ impl DowncastTo<(Wc, Wcs)> for Wcs { } } -impl UpcastFrom<()> for Wcs { - fn upcast_from((): ()) -> Self { - Wcs::t() - } -} - impl DowncastTo<()> for Wcs { fn downcast_to(&self) -> Option<()> { if self.set.is_empty() { diff --git a/fixme_tests/basic--impl_Debug_for_i32.rs b/fixme_tests/basic--impl_Debug_for_i32.rs index 4bf328fa..52b62a7d 100644 --- a/fixme_tests/basic--impl_Debug_for_i32.rs +++ b/fixme_tests/basic--impl_Debug_for_i32.rs @@ -3,9 +3,9 @@ const PROGRAM: &str = "[ crate core { - trait Debug<> where [] { } + trait Debug<> { } - impl<> Debug<> for i32 where [] { } + impl<> Debug<> for i32 { } } ]"; diff --git a/fixme_tests/basic--supertraits-hr.rs b/fixme_tests/basic--supertraits-hr.rs index 6c9c74b0..123d77e5 100644 --- a/fixme_tests/basic--supertraits-hr.rs +++ b/fixme_tests/basic--supertraits-hr.rs @@ -3,8 +3,8 @@ const PROGRAM: &str = "[ crate core { - trait Sub<> where [for Self: Super] { } - trait Super where [] { } + trait Sub<> where for Self: Super { } + trait Super { } } ]"; diff --git a/fixme_tests/basic--supertraits.rs b/fixme_tests/basic--supertraits.rs index b64c60e6..b0bf7fdf 100644 --- a/fixme_tests/basic--supertraits.rs +++ b/fixme_tests/basic--supertraits.rs @@ -3,12 +3,12 @@ const PROGRAM: &str = "[ crate core { - trait Eq<> where [Self: PartialEq<>] { } - trait PartialEq<> where [] { } + trait Eq<> where Self: PartialEq<> { } + trait PartialEq<> { } // ComparableBase is a supertype, but `T: Eq` is not. - trait Comparable where [T: Eq<>, Self: ComparableBase<>] { } - trait ComparableBase<> where [] { } + trait Comparable where T: Eq<>, Self: ComparableBase<> { } + trait ComparableBase<> { } } ]"; diff --git a/fixme_tests/coinductive.rs b/fixme_tests/coinductive.rs index 4baff7f4..91b91b65 100644 --- a/fixme_tests/coinductive.rs +++ b/fixme_tests/coinductive.rs @@ -6,11 +6,11 @@ fn magic_copy() { const PROGRAM: &str = "[ crate core { - struct Foo<> where [] {} - trait Copy<> where [] {} - trait Magic<> where [Self: Copy<>] {} + struct Foo<> {} + trait Copy<> {} + trait Magic<> where Self: Copy<> {} - impl Magic<> for T where [T: Magic<>] {} + impl Magic<> for T where T: Magic<> {} } ]"; @@ -37,13 +37,13 @@ fn magic_copy() { fn magic_copy_impl_for_all_copy() { const PROGRAM: &str = "[ crate core { - struct Foo<> where [] {} - struct Vec where [] {} + struct Foo<> {} + struct Vec {} - trait Copy<> where [] {} - trait Magic<> where [Self: Copy<>] {} + trait Copy<> {} + trait Magic<> where Self: Copy<> {} - impl Magic<> for T where [T: Copy<>] {} + impl Magic<> for T where T: Copy<> {} } ]"; @@ -82,17 +82,17 @@ fn magic_copy_impl_for_all_copy() { fn magic_vec_t() { const PROGRAM: &str = "[ crate core { - struct Foo<> where [] {} - struct Vec where [] {} + struct Foo<> {} + struct Vec {} - trait Copy<> where [] {} - trait Magic<> where [Self: Copy<>] {} + trait Copy<> {} + trait Magic<> where Self: Copy<> {} - impl Magic<> for Vec where [T: Magic<>] { + impl Magic<> for Vec where T: Magic<> { // FIXME: We need to test that this impl can prove T: Copy, // but how to do it? } - impl Copy<> for Vec where [T: Magic<>] {} + impl Copy<> for Vec where T: Magic<> {} } ]"; @@ -110,12 +110,12 @@ fn magic_vec_t() { // formality-rust // [(Rust/Program -// (term ([(crate TheCrate { (struct Foo[] where [] { (counter : i32) }) -// (struct Bar[] where [] { (counter : i32) }) +// (term ([(crate TheCrate { (struct Foo[] { (counter : i32) }) +// (struct Bar[] { (counter : i32) }) // (trait Magic[] where [(Self : Copy[])] {}) -// (trait Copy[] where [] {}) +// (trait Copy[] {}) // (impl[(type T)] Magic[] for T where [(T : Magic[])] {}) -// (impl[] Copy[] for (Bar < >) where [] {}) +// (impl[] Copy[] for (Bar < >) {}) // })] // TheCrate))) // ] @@ -158,12 +158,12 @@ fn magic_vec_t() { // redex-let* // formality-rust -// [(Rust/Program (term ([(crate C { (struct Foo[] where [] { (counter : i32) }) -// (struct Bar[] where [] { (counter : i32) }) +// [(Rust/Program (term ([(crate C { (struct Foo[] { (counter : i32) }) +// (struct Bar[] { (counter : i32) }) // (trait Magic[] where [(Self : Copy[])] {}) // (trait Copy[] where [(Self : Magic[])] {}) // (impl[(type T)] Magic[] for T where [(T : Magic[])] {}) -// (impl[] Copy[] for (Bar < >) where [] {}) +// (impl[] Copy[] for (Bar < >) {}) // })] C)))] // (; All decls in crate are considered 'ok'. @@ -209,13 +209,13 @@ fn magic_vec_t() { // (redex-let* // formality-rust -// [([Rust/CrateItemDecl_core ...] (term [(struct Foo[] where [] {}) -// (trait Copy[] where [] {}) -// (trait Partial[] where [(Self : Copy())] {}) -// (trait Complete[] where [(Self : Partial())] {}) -// (impl[(type T)] Partial[] for T where [(T : Complete())] {})])) +// [([Rust/CrateItemDecl_core ...] (term [(struct Foo[] {}) +// (trait Copy[] {}) +// (trait Partial[] where (Self : Copy()) {}) +// (trait Complete[] where (Self : Partial()) {}) +// (impl[(type T)] Partial[] for T where (T : Complete()) {})])) // (Rust/Program_A (term ([(crate A { Rust/CrateItemDecl_core ... -// (impl[(type T)] Complete[] for T where [] {}) +// (impl[(type T)] Complete[] for T {}) // })] // A))) // (Rust/Program_B (term ([(crate B { Rust/CrateItemDecl_core ... diff --git a/fixme_tests/impl-vs-trait-fn.rs b/fixme_tests/impl-vs-trait-fn.rs index 736cee9a..512bbf9d 100644 --- a/fixme_tests/impl-vs-trait-fn.rs +++ b/fixme_tests/impl-vs-trait-fn.rs @@ -6,15 +6,15 @@ fn absolutely_same() { const PROGRAM: &str = "[ crate core { - trait Debug<> where [] {} - trait Display<> where [] {} + trait Debug<> {} + trait Display<> {} - trait Get<> where [] { + trait Get<> { fn get(&mut l T) -> () where [T: Debug<>]; } - impl<> Get<> for () where [] { - fn get(&mut l T) -> () where [T: Debug<>] { + impl<> Get<> for () { + fn get(&mut l T) -> () where T: Debug<> { trusted } } @@ -34,16 +34,16 @@ fn absolutely_same() { fn different_self_type_mut_vs_sh() { const PROGRAM: &str = "[ crate core { - trait Debug<> where [] {} - trait Display<> where [] {} + trait Debug<> {} + trait Display<> {} - trait Get<> where [] { + trait Get<> { fn get(&mut l T) -> () where [T: Debug<>]; // -------- } - impl<> Get<> for () where [] { - fn get(&l T) -> () where [T: Debug<>] { + impl<> Get<> for () { + fn get(&l T) -> () where T: Debug<> { // ---- trusted } @@ -54,7 +54,7 @@ fn different_self_type_mut_vs_sh() { expect_test::expect![[r#" Err( Error { - context: "check_trait_impl(impl <> Get((rigid tuple(0))) where [] { fn get [(rigid &(shared) ^lt0_1 ^ty0_0)] -> (rigid tuple(0)) where [well_formed((rigid &(shared) ^lt0_1 ^ty0_0)), well_formed((rigid tuple(0))), is_implemented(Debug(^ty0_0))] })", + context: "check_trait_impl(impl <> Get((rigid tuple(0))) { fn get [(rigid &(shared) ^lt0_1 ^ty0_0)] -> (rigid tuple(0)) where [well_formed((rigid &(shared) ^lt0_1 ^ty0_0)), well_formed((rigid tuple(0))), is_implemented(Debug(^ty0_0))] })", source: Error { context: "check_fn_in_impl", source: "could not prove `sub((rigid &(mut) !ltU(2)_1 !tyU(2)_0), (rigid &(shared) !ltU(2)_1 !tyU(2)_0))` given `[\n well_formed((rigid &(shared) !ltU(2)_1 !tyU(2)_0)),\n well_formed((rigid tuple(0))),\n is_implemented(Debug(!tyU(2)_0)),\n]`", @@ -70,16 +70,16 @@ fn different_self_type_mut_vs_sh() { fn different_arg_type_u32_vs_i32() { const PROGRAM: &str = "[ crate core { - trait Debug<> where [] {} - trait Display<> where [] {} + trait Debug<> {} + trait Display<> {} - trait Get<> where [] { + trait Get<> { fn get(&mut l T, u32) -> () where [T: Debug<>]; // --- } - impl<> Get<> for () where [] { - fn get(&mut l T, i32) -> () where [T: Debug<>] { + impl<> Get<> for () { + fn get(&mut l T, i32) -> () where T: Debug<> { // --- trusted } @@ -90,7 +90,7 @@ fn different_arg_type_u32_vs_i32() { expect_test::expect![[r#" Err( Error { - context: "check_trait_impl(impl <> Get((rigid tuple(0))) where [] { fn get [(rigid &(mut) ^lt0_1 ^ty0_0), (rigid (scalar i32))] -> (rigid tuple(0)) where [well_formed((rigid &(mut) ^lt0_1 ^ty0_0)), well_formed((rigid (scalar i32))), well_formed((rigid tuple(0))), is_implemented(Debug(^ty0_0))] })", + context: "check_trait_impl(impl <> Get((rigid tuple(0))) { fn get [(rigid &(mut) ^lt0_1 ^ty0_0), (rigid (scalar i32))] -> (rigid tuple(0)) where [well_formed((rigid &(mut) ^lt0_1 ^ty0_0)), well_formed((rigid (scalar i32))), well_formed((rigid tuple(0))), is_implemented(Debug(^ty0_0))] })", source: Error { context: "check_fn_in_impl", source: "could not prove `sub((rigid (scalar u32)), (rigid (scalar i32)))` given `[\n well_formed((rigid &(mut) !ltU(2)_1 !tyU(2)_0)),\n well_formed((rigid (scalar i32))),\n well_formed((rigid tuple(0))),\n is_implemented(Debug(!tyU(2)_0)),\n]`", diff --git a/fixme_tests/wf-impl--supertrait-required.rs b/fixme_tests/wf-impl--supertrait-required.rs index d4db92fc..85b06025 100644 --- a/fixme_tests/wf-impl--supertrait-required.rs +++ b/fixme_tests/wf-impl--supertrait-required.rs @@ -6,16 +6,16 @@ fn test_one_impl() { const PROGRAM: &str = "[ crate core { - trait Eq<> where [Self: PartialEq<>] { } - trait PartialEq<> where [] { } - impl<> Eq<> for u32 where [] { } + trait Eq<> where Self: PartialEq<> { } + trait PartialEq<> { } + impl<> Eq<> for u32 { } } ]"; expect_test::expect![[r#" Err( Error { - context: "check_trait_impl(impl <> Eq((rigid (scalar u32))) where [] { })", + context: "check_trait_impl(impl <> Eq((rigid (scalar u32))) { })", source: "could not prove `is_implemented(Eq((rigid (scalar u32))))` given `[]`", }, ) @@ -28,10 +28,10 @@ fn test_one_impl() { fn test_both_impls() { const PROGRAM: &str = "[ crate core { - trait Eq<> where [Self: PartialEq<>] { } - trait PartialEq<> where [] { } - impl<> Eq<> for u32 where [] { } - impl<> PartialEq<> for u32 where [] { } + trait Eq<> where Self: PartialEq<> { } + trait PartialEq<> { } + impl<> Eq<> for u32 { } + impl<> PartialEq<> for u32 { } } ]"; diff --git a/tests/associated_type_normalization.rs b/tests/associated_type_normalization.rs index 95c95721..66dd57af 100644 --- a/tests/associated_type_normalization.rs +++ b/tests/associated_type_normalization.rs @@ -3,12 +3,12 @@ use formality_core::test; const MIRROR: &str = "[ crate core { - trait Mirror<> where [] { - type Assoc<> : [] where []; + trait Mirror<> { + type Assoc<> : []; } - impl Mirror<> for T where [] { - type Assoc<> = T where []; + impl Mirror<> for T { + type Assoc<> = T; } } ]"; diff --git a/tests/coherence_overlap.rs b/tests/coherence_overlap.rs index 114b4a62..566596ca 100644 --- a/tests/coherence_overlap.rs +++ b/tests/coherence_overlap.rs @@ -11,24 +11,24 @@ fn test_overlap_normalize_alias_to_LocalType() { let gen_program = |addl: &str| { const BASE_PROGRAM: &str = "[ crate core { - trait Iterator<> where [] { + trait Iterator<> { } - trait Mirror<> where [] { - type T<> : [] where []; + trait Mirror<> { + type T<> : []; } - impl Mirror<> for A where [] { - type T<> = A where []; + impl Mirror<> for A { + type T<> = A; } - struct LocalType<> where [] {} + struct LocalType<> {} - trait LocalTrait<> where [] { } + trait LocalTrait<> { } - impl LocalTrait<> for T where [T: Iterator<>] { } + impl LocalTrait<> for T where T: Iterator<> { } - impl<> LocalTrait<> for ::T where [] { } + impl<> LocalTrait<> for ::T { } ADDITIONAL } @@ -53,11 +53,11 @@ fn test_overlap_normalize_alias_to_LocalType() { expect_test::expect![[r#" Err( - "impls may overlap:\nimpl LocalTrait for ^ty0_0 where [^ty0_0 : Iterator] { }\nimpl LocalTrait for ::T where [] { }", + "impls may overlap:\nimpl LocalTrait for ^ty0_0 where ^ty0_0 : Iterator { }\nimpl LocalTrait for ::T { }", ) "#]] .assert_debug_eq(&test_program_ok(&gen_program( - "impl<> Iterator<> for LocalType<> where [] {}", + "impl<> Iterator<> for LocalType<> {}", ))); } @@ -69,24 +69,24 @@ fn test_overlap_alias_not_normalizable() { let gen_program = |addl: &str| { const BASE_PROGRAM: &str = "[ crate core { - trait Iterator<> where [] { + trait Iterator<> { } - trait Mirror<> where [] { - type T<> : [] where []; + trait Mirror<> { + type T<> : []; } - impl Mirror<> for A where [] { - type T<> = A where []; + impl Mirror<> for A { + type T<> = A; } - struct LocalType<> where [] {} + struct LocalType<> {} - trait LocalTrait<> where [] { } + trait LocalTrait<> { } - impl LocalTrait<> for T where [T: Iterator<>] { } + impl LocalTrait<> for T where T: Iterator<> { } - impl LocalTrait<> for ::T where [T: Mirror<>] { } + impl LocalTrait<> for ::T where T: Mirror<> { } ADDITIONAL } @@ -114,10 +114,10 @@ fn test_overlap_alias_not_normalizable() { expect_test::expect![[r#" Err( - "impls may overlap:\nimpl LocalTrait for ^ty0_0 where [^ty0_0 : Iterator] { }\nimpl LocalTrait for <^ty0_0 as Mirror>::T where [^ty0_0 : Mirror] { }", + "impls may overlap:\nimpl LocalTrait for ^ty0_0 where ^ty0_0 : Iterator { }\nimpl LocalTrait for <^ty0_0 as Mirror>::T where ^ty0_0 : Mirror { }", ) "#]] // FIXME .assert_debug_eq(&test_program_ok(&gen_program( - "impl<> Iterator<> for u32 where[] {}", + "impl<> Iterator<> for u32 {}", ))); } diff --git a/tests/projection.rs b/tests/projection.rs index ea3cd005..c4fce93a 100644 --- a/tests/projection.rs +++ b/tests/projection.rs @@ -2,16 +2,16 @@ use a_mir_formality::test_where_clause; const NORMALIZE_BASIC: &str = "[ crate test { - trait Iterator<> where [] { - type Item<> : [] where []; + trait Iterator<> { + type Item<> : []; } - struct Vec where [] {} + struct Vec {} - struct Foo<> where [] {} + struct Foo<> {} - impl Iterator<> for Vec where [] { - type Item<> = T where []; + impl Iterator<> for Vec { + type Item<> = T; } } ]"; @@ -184,24 +184,24 @@ fn normalize_basic() { const NORMALIZE_INTO_ITERATOR: &str = "[ crate test { - trait IntoIterator<> where [] { - type Item<> : [] where []; + trait IntoIterator<> { + type Item<> : []; } - trait Iterator<> where [] { - type Item<> : [] where []; + trait Iterator<> { + type Item<> : []; } - struct Vec where [] {} + struct Vec {} - struct Foo<> where [] {} + struct Foo<> {} - impl IntoIterator<> for Vec where [] { - type Item<> = T where []; + impl IntoIterator<> for Vec { + type Item<> = T; } - impl IntoIterator<> for T where [ T: Iterator<> ] { - type Item<> = ::Item<> where []; + impl IntoIterator<> for T where T: Iterator<> { + type Item<> = ::Item<>; } } ]"; @@ -248,14 +248,14 @@ fn normalize_into_iterator() { const PROJECTION_EQUALITY: &str = "[ crate test { - trait Trait1<> where [] { - type Type<> : [] where []; + trait Trait1<> { + type Type<> : []; } - trait Trait2 where [] {} - impl Trait2 for U where [ U: Trait1<>, ::Type => T ] {} - struct S<> where [] {} - impl<> Trait1<> for S<> where [] { - type Type<> = u32 where []; + trait Trait2 {} + impl Trait2 for U where U: Trait1<>, ::Type => T {} + struct S<> {} + impl<> Trait1<> for S<> { + type Type<> = u32; } } ]"; diff --git "a/tests/ui/basic_where_clauses_fail.\360\237\224\254" "b/tests/ui/basic_where_clauses_fail.\360\237\224\254" index 57d080d6..7b38d9a3 100644 --- "a/tests/ui/basic_where_clauses_fail.\360\237\224\254" +++ "b/tests/ui/basic_where_clauses_fail.\360\237\224\254" @@ -1,9 +1,9 @@ [ crate core { - trait A where [T: B<>] { } + trait A where T: B<> { } - trait B<> where [] { } + trait B<> { } - trait WellFormed<> where [for u32: A] { } + trait WellFormed<> where for u32: A { } } ] diff --git "a/tests/ui/basic_where_clauses_pass.\360\237\224\254" "b/tests/ui/basic_where_clauses_pass.\360\237\224\254" index cc9eb6f9..a8f83e94 100644 --- "a/tests/ui/basic_where_clauses_pass.\360\237\224\254" +++ "b/tests/ui/basic_where_clauses_pass.\360\237\224\254" @@ -1,12 +1,12 @@ //@check-pass [ crate core { - trait A where [T: B<>] { } + trait A where T: B<> { } - trait B<> where [] { } + trait B<> { } - trait WellFormed<> where [for u32: A] { } + trait WellFormed<> where for u32: A { } - impl B<> for T where [] {} + impl B<> for T {} } ] diff --git a/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.stderr b/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.stderr index 4261c067..cbb2768a 100644 --- a/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.stderr +++ b/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.stderr @@ -1,4 +1,4 @@ -Error: orphan_check(impl CoreTrait for CoreStruct where [] { }) +Error: orphan_check(impl CoreTrait for CoreStruct { }) Caused by: failed to prove {@ IsLocal(CoreTrait(CoreStruct))} given {}, got {} diff --git "a/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.\360\237\224\254" "b/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.\360\237\224\254" index 9f6ce0ff..3c59770d 100644 --- "a/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.\360\237\224\254" +++ "b/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.\360\237\224\254" @@ -1,9 +1,9 @@ [ crate core { - trait CoreTrait<> where [] {} - struct CoreStruct<> where [] {} + trait CoreTrait<> {} + struct CoreStruct<> {} }, crate foo { - impl<> CoreTrait<> for CoreStruct<> where [] {} + impl<> CoreTrait<> for CoreStruct<> {} } ] diff --git a/tests/ui/coherence_orphan/alias_to_unit.stderr b/tests/ui/coherence_orphan/alias_to_unit.stderr index 6dd0cc6f..17fcdc72 100644 --- a/tests/ui/coherence_orphan/alias_to_unit.stderr +++ b/tests/ui/coherence_orphan/alias_to_unit.stderr @@ -1,4 +1,4 @@ -Error: orphan_check(impl CoreTrait for ::Assoc where [] { }) +Error: orphan_check(impl CoreTrait for ::Assoc { }) Caused by: failed to prove {@ IsLocal(CoreTrait(::Assoc))} given {}, got {} diff --git "a/tests/ui/coherence_orphan/alias_to_unit.\360\237\224\254" "b/tests/ui/coherence_orphan/alias_to_unit.\360\237\224\254" index b08deb0d..1ff15ea5 100644 --- "a/tests/ui/coherence_orphan/alias_to_unit.\360\237\224\254" +++ "b/tests/ui/coherence_orphan/alias_to_unit.\360\237\224\254" @@ -1,17 +1,17 @@ [ crate core { - trait CoreTrait<> where [] {} + trait CoreTrait<> {} - trait Unit<> where [] { - type Assoc<> : [] where []; + trait Unit<> { + type Assoc<> : []; } - impl Unit<> for T where [] { - type Assoc<> = () where []; + impl Unit<> for T { + type Assoc<> = (); } }, crate foo { - struct FooStruct<> where [] {} - impl<> CoreTrait<> for as Unit<>>::Assoc<> where [] {} + struct FooStruct<> {} + impl<> CoreTrait<> for as Unit<>>::Assoc<> {} } ] diff --git "a/tests/ui/coherence_orphan/covered_VecT.\360\237\224\254" "b/tests/ui/coherence_orphan/covered_VecT.\360\237\224\254" index c30433ac..bdfe4c46 100644 --- "a/tests/ui/coherence_orphan/covered_VecT.\360\237\224\254" +++ "b/tests/ui/coherence_orphan/covered_VecT.\360\237\224\254" @@ -1,11 +1,11 @@ //@check-pass [ crate core { - trait CoreTrait where [] {} - struct Vec where [] {} + trait CoreTrait {} + struct Vec {} }, crate foo { - struct FooStruct<> where [] {} - impl CoreTrait> for Vec where [] {} + struct FooStruct<> {} + impl CoreTrait> for Vec {} } ] diff --git a/tests/ui/coherence_orphan/mirror_CoreStruct.stderr b/tests/ui/coherence_orphan/mirror_CoreStruct.stderr index 42288056..1c5f6b4e 100644 --- a/tests/ui/coherence_orphan/mirror_CoreStruct.stderr +++ b/tests/ui/coherence_orphan/mirror_CoreStruct.stderr @@ -1,4 +1,4 @@ -Error: orphan_check(impl CoreTrait for ::Assoc where [] { }) +Error: orphan_check(impl CoreTrait for ::Assoc { }) Caused by: failed to prove {@ IsLocal(CoreTrait(::Assoc))} given {}, got {} diff --git "a/tests/ui/coherence_orphan/mirror_CoreStruct.\360\237\224\254" "b/tests/ui/coherence_orphan/mirror_CoreStruct.\360\237\224\254" index ed764190..96cb16fd 100644 --- "a/tests/ui/coherence_orphan/mirror_CoreStruct.\360\237\224\254" +++ "b/tests/ui/coherence_orphan/mirror_CoreStruct.\360\237\224\254" @@ -1,17 +1,17 @@ [ crate core { - trait CoreTrait<> where [] {} - struct CoreStruct<> where [] {} + trait CoreTrait<> {} + struct CoreStruct<> {} - trait Mirror<> where [] { - type Assoc<> : [] where []; + trait Mirror<> { + type Assoc<> : []; } - impl Mirror<> for T where [] { - type Assoc<> = T where []; + impl Mirror<> for T { + type Assoc<> = T; } }, crate foo { - impl<> CoreTrait<> for as Mirror<>>::Assoc<> where [] {} + impl<> CoreTrait<> for as Mirror<>>::Assoc<> {} } ] diff --git "a/tests/ui/coherence_orphan/mirror_FooStruct.\360\237\224\254" "b/tests/ui/coherence_orphan/mirror_FooStruct.\360\237\224\254" index 28a81c00..f8cdd8b1 100644 --- "a/tests/ui/coherence_orphan/mirror_FooStruct.\360\237\224\254" +++ "b/tests/ui/coherence_orphan/mirror_FooStruct.\360\237\224\254" @@ -1,18 +1,18 @@ //@check-pass [ crate core { - trait CoreTrait<> where [] {} + trait CoreTrait<> {} - trait Mirror<> where [] { - type Assoc<> : [] where []; + trait Mirror<> { + type Assoc<> : []; } - impl Mirror<> for T where [] { - type Assoc<> = T where []; + impl Mirror<> for T { + type Assoc<> = T; } }, crate foo { - struct FooStruct<> where [] {} - impl<> CoreTrait<> for as Mirror<>>::Assoc<> where [] {} + struct FooStruct<> {} + impl<> CoreTrait<> for as Mirror<>>::Assoc<> {} } ] diff --git a/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.stderr b/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.stderr index 0136c2a5..e5415d69 100644 --- a/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.stderr +++ b/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.stderr @@ -1,4 +1,4 @@ -Error: orphan_check_neg(impl ! CoreTrait for CoreStruct where [] {}) +Error: orphan_check_neg(impl ! CoreTrait for CoreStruct {}) Caused by: failed to prove {@ IsLocal(CoreTrait(CoreStruct))} given {}, got {} diff --git "a/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.\360\237\224\254" "b/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.\360\237\224\254" index bfc1e54e..ac962247 100644 --- "a/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.\360\237\224\254" +++ "b/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.\360\237\224\254" @@ -1,9 +1,9 @@ [ crate core { - trait CoreTrait<> where [] {} - struct CoreStruct<> where [] {} + trait CoreTrait<> {} + struct CoreStruct<> {} }, crate foo { - impl<> !CoreTrait<> for CoreStruct<> where [] {} + impl<> !CoreTrait<> for CoreStruct<> {} } ] diff --git a/tests/ui/coherence_orphan/uncovered_T.stderr b/tests/ui/coherence_orphan/uncovered_T.stderr index b9b68e73..faeeb406 100644 --- a/tests/ui/coherence_orphan/uncovered_T.stderr +++ b/tests/ui/coherence_orphan/uncovered_T.stderr @@ -1,4 +1,4 @@ -Error: orphan_check(impl CoreTrait for ^ty0_0 where [] { }) +Error: orphan_check(impl CoreTrait for ^ty0_0 { }) Caused by: failed to prove {@ IsLocal(CoreTrait(!ty_1, FooStruct))} given {}, got {} diff --git "a/tests/ui/coherence_orphan/uncovered_T.\360\237\224\254" "b/tests/ui/coherence_orphan/uncovered_T.\360\237\224\254" index fa78976b..7f94d974 100644 --- "a/tests/ui/coherence_orphan/uncovered_T.\360\237\224\254" +++ "b/tests/ui/coherence_orphan/uncovered_T.\360\237\224\254" @@ -1,9 +1,9 @@ [ crate core { - trait CoreTrait where [] {} + trait CoreTrait {} }, crate foo { - struct FooStruct<> where [] {} - impl CoreTrait> for T where [] {} + struct FooStruct<> {} + impl CoreTrait> for T {} } ] diff --git a/tests/ui/coherence_overlap/T_where_Foo_not_u32_impls.stderr b/tests/ui/coherence_overlap/T_where_Foo_not_u32_impls.stderr index c39b8a81..39bd5e7b 100644 --- a/tests/ui/coherence_overlap/T_where_Foo_not_u32_impls.stderr +++ b/tests/ui/coherence_overlap/T_where_Foo_not_u32_impls.stderr @@ -1,4 +1,4 @@ -Error: check_trait_impl(impl Foo for ^ty0_0 where [^ty0_0 : Foo] { }) +Error: check_trait_impl(impl Foo for ^ty0_0 where ^ty0_0 : Foo { }) Caused by: failed to disprove diff --git "a/tests/ui/coherence_overlap/T_where_Foo_not_u32_impls.\360\237\224\254" "b/tests/ui/coherence_overlap/T_where_Foo_not_u32_impls.\360\237\224\254" index d6d1b295..fb034f80 100644 --- "a/tests/ui/coherence_overlap/T_where_Foo_not_u32_impls.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/T_where_Foo_not_u32_impls.\360\237\224\254" @@ -5,8 +5,8 @@ // was erroneously accepted by an earlier variant of negative impls. [ crate core { - trait Foo<> where [] {} - impl Foo<> for T where [T: Foo<>] {} - impl<> !Foo<> for u32 where [] {} + trait Foo<> {} + impl Foo<> for T where T: Foo<> {} + impl<> !Foo<> for u32 {} } ] diff --git a/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.stderr b/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.stderr index 0c464196..34f60b86 100644 --- a/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.stderr +++ b/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.stderr @@ -1,3 +1,3 @@ Error: impls may overlap: -impl FooTrait for ^ty0_0 where [^ty0_0 : CoreTrait] { } -impl FooTrait for CoreStruct where [] { } +impl FooTrait for ^ty0_0 where ^ty0_0 : CoreTrait { } +impl FooTrait for CoreStruct { } diff --git "a/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.\360\237\224\254" "b/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.\360\237\224\254" index b70143ea..f908376e 100644 --- "a/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.\360\237\224\254" @@ -1,11 +1,11 @@ [ crate core { - trait CoreTrait<> where [] {} - struct CoreStruct<> where [] {} + trait CoreTrait<> {} + struct CoreStruct<> {} }, crate foo { - trait FooTrait<> where [] {} - impl FooTrait<> for T where [T: CoreTrait<>] {} - impl<> FooTrait<> for CoreStruct<> where [] {} + trait FooTrait<> {} + impl FooTrait<> for T where T: CoreTrait<> {} + impl<> FooTrait<> for CoreStruct<> {} } ] diff --git "a/tests/ui/coherence_overlap/neg_CoreTrait_for_CoreStruct_implies_no_overlap.\360\237\224\254" "b/tests/ui/coherence_overlap/neg_CoreTrait_for_CoreStruct_implies_no_overlap.\360\237\224\254" index ccfc44dd..c87ce1cf 100644 --- "a/tests/ui/coherence_overlap/neg_CoreTrait_for_CoreStruct_implies_no_overlap.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/neg_CoreTrait_for_CoreStruct_implies_no_overlap.\360\237\224\254" @@ -3,13 +3,13 @@ // where there is a negative impl, so it is accepted. [ crate core { - trait CoreTrait<> where [] {} - struct CoreStruct<> where [] {} - impl<> !CoreTrait<> for CoreStruct<> where [] {} + trait CoreTrait<> {} + struct CoreStruct<> {} + impl<> !CoreTrait<> for CoreStruct<> {} }, crate foo { - trait FooTrait<> where [] {} - impl FooTrait<> for T where [T: CoreTrait<>] {} - impl<> FooTrait<> for CoreStruct<> where [] {} + trait FooTrait<> {} + impl FooTrait<> for T where T: CoreTrait<> {} + impl<> FooTrait<> for CoreStruct<> {} } ] diff --git a/tests/ui/coherence_overlap/u32_T_impls.stderr b/tests/ui/coherence_overlap/u32_T_impls.stderr index 8d63fc96..2ec470b1 100644 --- a/tests/ui/coherence_overlap/u32_T_impls.stderr +++ b/tests/ui/coherence_overlap/u32_T_impls.stderr @@ -1,3 +1,3 @@ Error: impls may overlap: -impl Foo for u32 where [] { } -impl Foo for ^ty0_0 where [] { } +impl Foo for u32 { } +impl Foo for ^ty0_0 { } diff --git "a/tests/ui/coherence_overlap/u32_T_impls.\360\237\224\254" "b/tests/ui/coherence_overlap/u32_T_impls.\360\237\224\254" index 9aaf9f06..9b0afc3e 100644 --- "a/tests/ui/coherence_overlap/u32_T_impls.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/u32_T_impls.\360\237\224\254" @@ -1,7 +1,7 @@ [ crate core { - trait Foo<> where [] {} - impl<> Foo<> for u32 where [] {} - impl Foo<> for T where [] {} + trait Foo<> {} + impl<> Foo<> for u32 {} + impl Foo<> for T {} } ] diff --git a/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.stderr b/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.stderr index 2d6e18de..aa6c5ed2 100644 --- a/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.stderr +++ b/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.stderr @@ -1,3 +1,3 @@ Error: impls may overlap: -impl Foo for u32 where [] { } -impl Foo for ^ty0_0 where [^ty0_0 : Is] { } +impl Foo for u32 { } +impl Foo for ^ty0_0 where ^ty0_0 : Is { } diff --git "a/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.\360\237\224\254" "b/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.\360\237\224\254" index 149d9d05..9f289098 100644 --- "a/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.\360\237\224\254" @@ -2,11 +2,11 @@ // and also all `T: Is`, and `u32: Is`. [ crate core { - trait Foo<> where [] {} - impl<> Foo<> for u32 where [] {} - impl Foo<> for T where [T: Is<>] {} + trait Foo<> {} + impl<> Foo<> for u32 {} + impl Foo<> for T where T: Is<> {} - trait Is<> where [] {} - impl<> Is<> for u32 where [] {} + trait Is<> {} + impl<> Is<> for u32 {} } ] diff --git "a/tests/ui/coherence_overlap/u32_T_where_T_Not_impls.\360\237\224\254" "b/tests/ui/coherence_overlap/u32_T_where_T_Not_impls.\360\237\224\254" index 4e44d346..eba474e6 100644 --- "a/tests/ui/coherence_overlap/u32_T_where_T_Not_impls.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/u32_T_where_T_Not_impls.\360\237\224\254" @@ -6,10 +6,10 @@ // See also test_foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait [ crate core { - trait Foo<> where [] {} - impl<> Foo<> for u32 where [] {} - impl Foo<> for T where [T: Not<>] {} + trait Foo<> {} + impl<> Foo<> for u32 {} + impl Foo<> for T where T: Not<> {} - trait Not<> where [] {} + trait Not<> {} } ] diff --git "a/tests/ui/coherence_overlap/u32_i32_impls.\360\237\224\254" "b/tests/ui/coherence_overlap/u32_i32_impls.\360\237\224\254" index b85c62ff..4e6c14da 100644 --- "a/tests/ui/coherence_overlap/u32_i32_impls.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/u32_i32_impls.\360\237\224\254" @@ -1,8 +1,8 @@ //@check-pass [ crate core { - trait Foo<> where [] {} - impl<> Foo<> for u32 where [] {} - impl<> Foo<> for i32 where [] {} + trait Foo<> {} + impl<> Foo<> for u32 {} + impl<> Foo<> for i32 {} } ] diff --git a/tests/ui/coherence_overlap/u32_not_u32_impls.stderr b/tests/ui/coherence_overlap/u32_not_u32_impls.stderr index b0eed6f5..b7710f57 100644 --- a/tests/ui/coherence_overlap/u32_not_u32_impls.stderr +++ b/tests/ui/coherence_overlap/u32_not_u32_impls.stderr @@ -1,4 +1,4 @@ -Error: check_trait_impl(impl Foo for u32 where [] { }) +Error: check_trait_impl(impl Foo for u32 { }) Caused by: failed to disprove diff --git "a/tests/ui/coherence_overlap/u32_not_u32_impls.\360\237\224\254" "b/tests/ui/coherence_overlap/u32_not_u32_impls.\360\237\224\254" index 8140731b..6dfae3d7 100644 --- "a/tests/ui/coherence_overlap/u32_not_u32_impls.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/u32_not_u32_impls.\360\237\224\254" @@ -1,8 +1,8 @@ // Test that a positive and negative impl for the same type (`u32`, here) is rejected. [ crate core { - trait Foo<> where [] {} - impl<> Foo<> for u32 where [] {} - impl<> !Foo<> for u32 where [] {} + trait Foo<> {} + impl<> Foo<> for u32 {} + impl<> !Foo<> for u32 {} } ] diff --git a/tests/ui/coherence_overlap/u32_u32_impls.stderr b/tests/ui/coherence_overlap/u32_u32_impls.stderr index f1418eee..1e241644 100644 --- a/tests/ui/coherence_overlap/u32_u32_impls.stderr +++ b/tests/ui/coherence_overlap/u32_u32_impls.stderr @@ -1 +1 @@ -Error: duplicate impl in current crate: impl Foo for u32 where [] { } +Error: duplicate impl in current crate: impl Foo for u32 { } diff --git "a/tests/ui/coherence_overlap/u32_u32_impls.\360\237\224\254" "b/tests/ui/coherence_overlap/u32_u32_impls.\360\237\224\254" index c007b201..e61914c5 100644 --- "a/tests/ui/coherence_overlap/u32_u32_impls.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/u32_u32_impls.\360\237\224\254" @@ -1,7 +1,7 @@ [ crate core { - trait Foo<> where [] {} - impl<> Foo<> for u32 where [] {} - impl<> Foo<> for u32 where [] {} + trait Foo<> {} + impl<> Foo<> for u32 {} + impl<> Foo<> for u32 {} } ] diff --git a/tests/ui/consts/generic_mismatch.stderr b/tests/ui/consts/generic_mismatch.stderr index 187103b2..7732b05b 100644 --- a/tests/ui/consts/generic_mismatch.stderr +++ b/tests/ui/consts/generic_mismatch.stderr @@ -1,4 +1,4 @@ -Error: check_trait_impl(impl Foo for u32 where [type_of_const ^const0_0 is u32] { }) +Error: check_trait_impl(impl Foo for u32 where type_of_const ^const0_0 is u32 { }) Caused by: failed to prove {Foo(u32, const !const_1)} given {@ ConstHasType(!const_1 , u32)}, got {} diff --git "a/tests/ui/consts/generic_mismatch.\360\237\224\254" "b/tests/ui/consts/generic_mismatch.\360\237\224\254" index 00855203..217d22b9 100644 --- "a/tests/ui/consts/generic_mismatch.\360\237\224\254" +++ "b/tests/ui/consts/generic_mismatch.\360\237\224\254" @@ -1,7 +1,7 @@ [ crate Foo { - trait Foo where [type_of_const C is bool] {} + trait Foo where type_of_const C is bool {} - impl Foo for u32 where [type_of_const C is u32] {} + impl Foo for u32 where type_of_const C is u32 {} } ] diff --git "a/tests/ui/consts/holds.\360\237\224\254" "b/tests/ui/consts/holds.\360\237\224\254" index a124a028..7a52aafd 100644 --- "a/tests/ui/consts/holds.\360\237\224\254" +++ "b/tests/ui/consts/holds.\360\237\224\254" @@ -1,8 +1,8 @@ //@check-pass [ crate Foo { - trait Foo where [type_of_const C is bool] {} + trait Foo where type_of_const C is bool {} - impl<> Foo for u32 where [] {} + impl<> Foo for u32 {} } ] diff --git a/tests/ui/consts/mismatch.stderr b/tests/ui/consts/mismatch.stderr index 5f917490..7b5542e0 100644 --- a/tests/ui/consts/mismatch.stderr +++ b/tests/ui/consts/mismatch.stderr @@ -1,4 +1,4 @@ -Error: check_trait_impl(impl Foo for u32 where [] { }) +Error: check_trait_impl(impl Foo for u32 { }) Caused by: failed to prove {Foo(u32, const value(42, u32))} given {}, got {} diff --git "a/tests/ui/consts/mismatch.\360\237\224\254" "b/tests/ui/consts/mismatch.\360\237\224\254" index ba7598be..901bbad8 100644 --- "a/tests/ui/consts/mismatch.\360\237\224\254" +++ "b/tests/ui/consts/mismatch.\360\237\224\254" @@ -1,7 +1,7 @@ [ crate Foo { - trait Foo where [type_of_const C is bool] {} + trait Foo where type_of_const C is bool {} - impl<> Foo for u32 where [] {} + impl<> Foo for u32 {} } ] diff --git "a/tests/ui/consts/multiple_type_of_const.\360\237\224\254" "b/tests/ui/consts/multiple_type_of_const.\360\237\224\254" index 9a416ed9..b643b71c 100644 --- "a/tests/ui/consts/multiple_type_of_const.\360\237\224\254" +++ "b/tests/ui/consts/multiple_type_of_const.\360\237\224\254" @@ -5,6 +5,6 @@ //@check-pass [ crate Foo { - trait Foo where [type_of_const C is bool, type_of_const C is u32] {} + trait Foo where type_of_const C is bool, type_of_const C is u32 {} } ] diff --git "a/tests/ui/consts/nonsense_rigid_const_bound.\360\237\224\254" "b/tests/ui/consts/nonsense_rigid_const_bound.\360\237\224\254" index cf6cc59f..26824392 100644 --- "a/tests/ui/consts/nonsense_rigid_const_bound.\360\237\224\254" +++ "b/tests/ui/consts/nonsense_rigid_const_bound.\360\237\224\254" @@ -2,6 +2,6 @@ /// substituting and directly going to a wrong constant. [ crate Foo { - trait Foo<> where [type_of_const true is u32] {} + trait Foo<> where type_of_const true is u32 {} } ] diff --git "a/tests/ui/consts/ok.\360\237\224\254" "b/tests/ui/consts/ok.\360\237\224\254" index e565909d..e5311ebe 100644 --- "a/tests/ui/consts/ok.\360\237\224\254" +++ "b/tests/ui/consts/ok.\360\237\224\254" @@ -1,9 +1,9 @@ //@check-pass [ crate Foo { - trait Foo where [type_of_const C is bool] {} - trait Bar where [type_of_const C is u32] {} + trait Foo where type_of_const C is bool {} + trait Bar where type_of_const C is u32 {} - impl Foo for u32 where [type_of_const C is bool] {} + impl Foo for u32 where type_of_const C is bool {} } ] diff --git "a/tests/ui/consts/rigid_const_bound.\360\237\224\254" "b/tests/ui/consts/rigid_const_bound.\360\237\224\254" index 179a7030..210cb9db 100644 --- "a/tests/ui/consts/rigid_const_bound.\360\237\224\254" +++ "b/tests/ui/consts/rigid_const_bound.\360\237\224\254" @@ -3,6 +3,6 @@ //@check-pass [ crate Foo { - trait Foo<> where [type_of_const true is bool] {} + trait Foo<> where type_of_const true is bool {} } ] diff --git "a/tests/ui/fn/lifetime.\360\237\224\254" "b/tests/ui/fn/lifetime.\360\237\224\254" index e1f33550..59716040 100644 --- "a/tests/ui/fn/lifetime.\360\237\224\254" +++ "b/tests/ui/fn/lifetime.\360\237\224\254" @@ -2,6 +2,6 @@ [ crate Foo { // fn one_lt_arg<'a, T>(_: &'a T) -> () {} - fn one_lt_arg(&a T) -> () where [] { trusted } + fn one_lt_arg(&a T) -> () { trusted } } ] diff --git "a/tests/ui/fn/ok.\360\237\224\254" "b/tests/ui/fn/ok.\360\237\224\254" index cfa61a7e..57925027 100644 --- "a/tests/ui/fn/ok.\360\237\224\254" +++ "b/tests/ui/fn/ok.\360\237\224\254" @@ -3,18 +3,18 @@ [ crate Foo { // fn simple_fn() {} - fn simple_fn<>() -> () where [] { trusted } + fn simple_fn<>() -> () { trusted } // fn one_arg(_: T) {} - fn one_arg(T) -> () where [] { trusted } + fn one_arg(T) -> () { trusted } // fn one_ret(_: T) {} - fn one_ret() -> T where [] { trusted } + fn one_ret() -> T { trusted } // fn arg_ret(_: T) -> U {} - fn arg_ret(T) -> U where [] { trusted } + fn arg_ret(T) -> U { trusted } // fn multi_arg_ret(_: T, _: Y) -> (U, I) {} - fn multi_arg_ret(T, Y) -> (U, I) where [] { trusted } + fn multi_arg_ret(T, Y) -> (U, I) { trusted } } ] diff --git "a/tests/ui/hello_world.\360\237\224\254" "b/tests/ui/hello_world.\360\237\224\254" index 3a0bb8e4..fd0c81b6 100644 --- "a/tests/ui/hello_world.\360\237\224\254" +++ "b/tests/ui/hello_world.\360\237\224\254" @@ -1,15 +1,15 @@ //@check-pass [ crate Foo { - trait Foo where [T: Bar, Self: Baz<>] {} + trait Foo where T: Bar, Self: Baz<> {} - trait Bar where [T: Baz<>] {} + trait Bar where T: Baz<> {} - trait Baz<> where [] {} + trait Baz<> {} - impl<> Baz<> for u32 where [] {} + impl<> Baz<> for u32 {} - impl<> Bar for u32 where [] {} - impl Bar for () where [T: Baz<>] {} + impl<> Bar for u32 {} + impl Bar for () where T: Baz<> {} } ] diff --git "a/tests/ui/hello_world_fail.\360\237\224\254" "b/tests/ui/hello_world_fail.\360\237\224\254" index 4620deca..f6709f1d 100644 --- "a/tests/ui/hello_world_fail.\360\237\224\254" +++ "b/tests/ui/hello_world_fail.\360\237\224\254" @@ -1,9 +1,9 @@ [ crate Foo { - trait Foo where [T: Bar] {} + trait Foo where T: Bar {} - trait Bar where [T: Baz<>] {} + trait Bar where T: Baz<> {} - trait Baz<> where [] {} + trait Baz<> {} } ] diff --git a/tests/ui/parser.stderr b/tests/ui/parser.stderr index 61a957c7..a9017fad 100644 --- a/tests/ui/parser.stderr +++ b/tests/ui/parser.stderr @@ -1,13 +1,13 @@ Error: expected `:` Caused by: - 0: ] {} + 0: {} } ] 1: failed to parse [ crate Foo { - trait Baz<> where [ cake ] {} + trait Baz<> where cake {} } ] diff --git "a/tests/ui/parser.\360\237\224\254" "b/tests/ui/parser.\360\237\224\254" index 45f21b25..273407a4 100644 --- "a/tests/ui/parser.\360\237\224\254" +++ "b/tests/ui/parser.\360\237\224\254" @@ -1,5 +1,5 @@ [ crate Foo { - trait Baz<> where [ cake ] {} + trait Baz<> where cake {} } ] From 1dc3bdae28361f903eb838f487ec84288c00dff3 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 5 Nov 2023 05:41:09 -0500 Subject: [PATCH 15/38] accept defaulted binders in traits, remove `<>` We don't need this anymore. --- crates/formality-prove/src/test/adt_wf.rs | 2 +- crates/formality-prove/src/test/magic_copy.rs | 2 +- .../formality-prove/src/test/simple_impl.rs | 2 +- crates/formality-rust/src/trait_binder.rs | 15 ++++-- examples/formality-eg/grammar/test.rs | 2 +- fixme_tests/basic--impl_Debug_for_i32.rs | 8 +-- fixme_tests/basic--supertraits-hr.rs | 2 +- fixme_tests/basic--supertraits.rs | 8 +-- fixme_tests/coinductive.rs | 28 +++++----- fixme_tests/impl-vs-trait-fn.rs | 36 ++++++------- fixme_tests/wf-impl--supertrait-required.rs | 14 ++--- tests/associated_type_normalization.rs | 10 ++-- tests/coherence_overlap.rs | 40 +++++++------- tests/projection.rs | 52 +++++++++---------- ...basic_where_clauses_fail.\360\237\224\254" | 6 +-- ...basic_where_clauses_pass.\360\237\224\254" | 8 +-- ...it_for_CoreStruct_in_Foo.\360\237\224\254" | 6 +-- .../alias_to_unit.\360\237\224\254" | 14 ++--- .../covered_VecT.\360\237\224\254" | 4 +- .../mirror_CoreStruct.\360\237\224\254" | 14 ++--- .../mirror_FooStruct.\360\237\224\254" | 14 ++--- ...it_for_CoreStruct_in_Foo.\360\237\224\254" | 6 +-- .../uncovered_T.\360\237\224\254" | 4 +- ..._where_Foo_not_u32_impls.\360\237\224\254" | 6 +-- ..._does_not_impl_CoreTrait.\360\237\224\254" | 10 ++-- ...truct_implies_no_overlap.\360\237\224\254" | 12 ++--- .../u32_T_impls.\360\237\224\254" | 6 +-- .../u32_T_where_T_Is_impls.\360\237\224\254" | 10 ++-- .../u32_T_where_T_Not_impls.\360\237\224\254" | 8 +-- .../u32_i32_impls.\360\237\224\254" | 6 +-- .../u32_not_u32_impls.\360\237\224\254" | 6 +-- .../u32_u32_impls.\360\237\224\254" | 6 +-- "tests/ui/consts/holds.\360\237\224\254" | 2 +- "tests/ui/consts/mismatch.\360\237\224\254" | 2 +- ...nsense_rigid_const_bound.\360\237\224\254" | 2 +- .../rigid_const_bound.\360\237\224\254" | 2 +- "tests/ui/fn/ok.\360\237\224\254" | 2 +- "tests/ui/hello_world.\360\237\224\254" | 12 ++--- "tests/ui/hello_world_fail.\360\237\224\254" | 4 +- tests/ui/parser.stderr | 2 +- "tests/ui/parser.\360\237\224\254" | 2 +- 41 files changed, 203 insertions(+), 194 deletions(-) diff --git a/crates/formality-prove/src/test/adt_wf.rs b/crates/formality-prove/src/test/adt_wf.rs index d3bf00d9..5a633722 100644 --- a/crates/formality-prove/src/test/adt_wf.rs +++ b/crates/formality-prove/src/test/adt_wf.rs @@ -10,7 +10,7 @@ use crate::{decls::Decls, prove::prove}; fn decls() -> Decls { Decls { trait_decls: vec![term("trait Foo where {}")], - impl_decls: vec![term("impl<> Foo(u32) where {}")], + impl_decls: vec![term("impl Foo(u32) where {}")], adt_decls: vec![term("adt X where {Foo(T)}")], ..Decls::empty() } diff --git a/crates/formality-prove/src/test/magic_copy.rs b/crates/formality-prove/src/test/magic_copy.rs index 04ded95b..0dafe603 100644 --- a/crates/formality-prove/src/test/magic_copy.rs +++ b/crates/formality-prove/src/test/magic_copy.rs @@ -15,7 +15,7 @@ fn decls() -> Decls { ], impl_decls: vec![ term("impl Magic(T) where {Magic(T)}"), - term("impl<> Copy(u32) where {}"), + term("impl Copy(u32) where {}"), ], ..Decls::empty() } diff --git a/crates/formality-prove/src/test/simple_impl.rs b/crates/formality-prove/src/test/simple_impl.rs index ded1a560..774b312e 100644 --- a/crates/formality-prove/src/test/simple_impl.rs +++ b/crates/formality-prove/src/test/simple_impl.rs @@ -11,7 +11,7 @@ fn decls() -> Decls { trait_decls: vec![term("trait Debug where {}")], impl_decls: vec![ term("impl Debug(Vec) where {Debug(T)}"), - term("impl<> Debug(u32) where {}"), + term("impl Debug(u32) where {}"), ], ..Decls::empty() } diff --git a/crates/formality-rust/src/trait_binder.rs b/crates/formality-rust/src/trait_binder.rs index bd947f7f..8647a10d 100644 --- a/crates/formality-rust/src/trait_binder.rs +++ b/crates/formality-rust/src/trait_binder.rs @@ -79,6 +79,7 @@ where T: Term, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // In Debug output, we include the `Self` to avoid confusion -- is this good? write!(f, "{:?}", self.explicit_binder) } } @@ -90,9 +91,17 @@ where #[tracing::instrument(level = "trace", ret)] fn parse<'t>(scope: &Scope, text: &'t str) -> ParseResult<'t, Self> { Parser::single_variant(scope, text, "TraitBinder", |p| { - p.expect_char('<')?; - let mut bindings: Vec> = p.comma_nonterminal()?; - p.expect_char('>')?; + let mut bindings = match p.expect_char('<') { + Ok(()) => { + let bindings: Vec> = p.comma_nonterminal()?; + p.expect_char('>')?; + bindings + } + Err(_) => { + // If we don't see a `<`, assume there are no add'l bound variables. + vec![] + } + }; // insert the `Self` binding at position 0 let bound_var = BoundVar::fresh(ParameterKind::Ty); diff --git a/examples/formality-eg/grammar/test.rs b/examples/formality-eg/grammar/test.rs index 59e9c225..4f60139f 100644 --- a/examples/formality-eg/grammar/test.rs +++ b/examples/formality-eg/grammar/test.rs @@ -15,7 +15,7 @@ fn test_struct_decl() { #[test] fn test_struct_ty_empty_args() { - let r: Ty = term("Point<>"); + let r: Ty = term("Point"); expect_test::expect![[r#" Point "#]] diff --git a/fixme_tests/basic--impl_Debug_for_i32.rs b/fixme_tests/basic--impl_Debug_for_i32.rs index 52b62a7d..88bb64af 100644 --- a/fixme_tests/basic--impl_Debug_for_i32.rs +++ b/fixme_tests/basic--impl_Debug_for_i32.rs @@ -3,9 +3,9 @@ const PROGRAM: &str = "[ crate core { - trait Debug<> { } + trait Debug { } - impl<> Debug<> for i32 { } + impl Debug for i32 { } } ]"; @@ -19,7 +19,7 @@ fn test_i32() { "#]] .assert_debug_eq(&formality_rust::test_can_prove_where_clause( PROGRAM, - "i32: Debug<>", + "i32: Debug", )); } @@ -33,6 +33,6 @@ fn test_u32() { "#]] .assert_debug_eq(&formality_rust::test_can_prove_where_clause( PROGRAM, - "u32: Debug<>", + "u32: Debug", )); } diff --git a/fixme_tests/basic--supertraits-hr.rs b/fixme_tests/basic--supertraits-hr.rs index 123d77e5..b5f014a1 100644 --- a/fixme_tests/basic--supertraits-hr.rs +++ b/fixme_tests/basic--supertraits-hr.rs @@ -3,7 +3,7 @@ const PROGRAM: &str = "[ crate core { - trait Sub<> where for Self: Super { } + trait Sub where for Self: Super { } trait Super { } } ]"; diff --git a/fixme_tests/basic--supertraits.rs b/fixme_tests/basic--supertraits.rs index b0bf7fdf..bfa82f2c 100644 --- a/fixme_tests/basic--supertraits.rs +++ b/fixme_tests/basic--supertraits.rs @@ -3,12 +3,12 @@ const PROGRAM: &str = "[ crate core { - trait Eq<> where Self: PartialEq<> { } - trait PartialEq<> { } + trait Eq where Self: PartialEq { } + trait PartialEq { } // ComparableBase is a supertype, but `T: Eq` is not. - trait Comparable where T: Eq<>, Self: ComparableBase<> { } - trait ComparableBase<> { } + trait Comparable where T: Eq, Self: ComparableBase { } + trait ComparableBase { } } ]"; diff --git a/fixme_tests/coinductive.rs b/fixme_tests/coinductive.rs index 91b91b65..60887ffb 100644 --- a/fixme_tests/coinductive.rs +++ b/fixme_tests/coinductive.rs @@ -6,11 +6,11 @@ fn magic_copy() { const PROGRAM: &str = "[ crate core { - struct Foo<> {} - trait Copy<> {} - trait Magic<> where Self: Copy<> {} + struct Foo {} + trait Copy {} + trait Magic where Self: Copy {} - impl Magic<> for T where T: Magic<> {} + impl Magic for T where T: Magic {} } ]"; @@ -28,7 +28,7 @@ fn magic_copy() { "#]] .assert_debug_eq(&formality_rust::test_can_prove_where_clause( PROGRAM, - "Foo: Magic<>", + "Foo: Magic", )); } @@ -37,13 +37,13 @@ fn magic_copy() { fn magic_copy_impl_for_all_copy() { const PROGRAM: &str = "[ crate core { - struct Foo<> {} + struct Foo {} struct Vec {} - trait Copy<> {} - trait Magic<> where Self: Copy<> {} + trait Copy {} + trait Magic where Self: Copy {} - impl Magic<> for T where T: Copy<> {} + impl Magic for T where T: Copy {} } ]"; @@ -82,17 +82,17 @@ fn magic_copy_impl_for_all_copy() { fn magic_vec_t() { const PROGRAM: &str = "[ crate core { - struct Foo<> {} + struct Foo {} struct Vec {} - trait Copy<> {} - trait Magic<> where Self: Copy<> {} + trait Copy {} + trait Magic where Self: Copy {} - impl Magic<> for Vec where T: Magic<> { + impl Magic for Vec where T: Magic { // FIXME: We need to test that this impl can prove T: Copy, // but how to do it? } - impl Copy<> for Vec where T: Magic<> {} + impl Copy for Vec where T: Magic {} } ]"; diff --git a/fixme_tests/impl-vs-trait-fn.rs b/fixme_tests/impl-vs-trait-fn.rs index 512bbf9d..ffa48d6e 100644 --- a/fixme_tests/impl-vs-trait-fn.rs +++ b/fixme_tests/impl-vs-trait-fn.rs @@ -6,15 +6,15 @@ fn absolutely_same() { const PROGRAM: &str = "[ crate core { - trait Debug<> {} - trait Display<> {} + trait Debug {} + trait Display {} - trait Get<> { - fn get(&mut l T) -> () where [T: Debug<>]; + trait Get { + fn get(&mut l T) -> () where [T: Debug]; } - impl<> Get<> for () { - fn get(&mut l T) -> () where T: Debug<> { + impl Get for () { + fn get(&mut l T) -> () where T: Debug { trusted } } @@ -34,16 +34,16 @@ fn absolutely_same() { fn different_self_type_mut_vs_sh() { const PROGRAM: &str = "[ crate core { - trait Debug<> {} - trait Display<> {} + trait Debug {} + trait Display {} - trait Get<> { - fn get(&mut l T) -> () where [T: Debug<>]; + trait Get { + fn get(&mut l T) -> () where [T: Debug]; // -------- } - impl<> Get<> for () { - fn get(&l T) -> () where T: Debug<> { + impl Get for () { + fn get(&l T) -> () where T: Debug { // ---- trusted } @@ -70,16 +70,16 @@ fn different_self_type_mut_vs_sh() { fn different_arg_type_u32_vs_i32() { const PROGRAM: &str = "[ crate core { - trait Debug<> {} - trait Display<> {} + trait Debug {} + trait Display {} - trait Get<> { - fn get(&mut l T, u32) -> () where [T: Debug<>]; + trait Get { + fn get(&mut l T, u32) -> () where [T: Debug]; // --- } - impl<> Get<> for () { - fn get(&mut l T, i32) -> () where T: Debug<> { + impl Get for () { + fn get(&mut l T, i32) -> () where T: Debug { // --- trusted } diff --git a/fixme_tests/wf-impl--supertrait-required.rs b/fixme_tests/wf-impl--supertrait-required.rs index 85b06025..48d70a7a 100644 --- a/fixme_tests/wf-impl--supertrait-required.rs +++ b/fixme_tests/wf-impl--supertrait-required.rs @@ -6,9 +6,9 @@ fn test_one_impl() { const PROGRAM: &str = "[ crate core { - trait Eq<> where Self: PartialEq<> { } - trait PartialEq<> { } - impl<> Eq<> for u32 { } + trait Eq where Self: PartialEq { } + trait PartialEq { } + impl Eq for u32 { } } ]"; @@ -28,10 +28,10 @@ fn test_one_impl() { fn test_both_impls() { const PROGRAM: &str = "[ crate core { - trait Eq<> where Self: PartialEq<> { } - trait PartialEq<> { } - impl<> Eq<> for u32 { } - impl<> PartialEq<> for u32 { } + trait Eq where Self: PartialEq { } + trait PartialEq { } + impl Eq for u32 { } + impl PartialEq for u32 { } } ]"; diff --git a/tests/associated_type_normalization.rs b/tests/associated_type_normalization.rs index 66dd57af..e7a18c2f 100644 --- a/tests/associated_type_normalization.rs +++ b/tests/associated_type_normalization.rs @@ -3,12 +3,12 @@ use formality_core::test; const MIRROR: &str = "[ crate core { - trait Mirror<> { - type Assoc<> : []; + trait Mirror { + type Assoc : []; } - impl Mirror<> for T { - type Assoc<> = T; + impl Mirror for T { + type Assoc = T; } } ]"; @@ -47,6 +47,6 @@ fn test_mirror_normalizes_u32_to_u32() { "#]] .assert_debug_eq(&test_where_clause( MIRROR, - "exists {} => {::Assoc<> = T}", + "exists {} => {::Assoc = T}", )); } diff --git a/tests/coherence_overlap.rs b/tests/coherence_overlap.rs index 566596ca..720ef2f0 100644 --- a/tests/coherence_overlap.rs +++ b/tests/coherence_overlap.rs @@ -11,24 +11,24 @@ fn test_overlap_normalize_alias_to_LocalType() { let gen_program = |addl: &str| { const BASE_PROGRAM: &str = "[ crate core { - trait Iterator<> { + trait Iterator { } - trait Mirror<> { - type T<> : []; + trait Mirror { + type T : []; } - impl Mirror<> for A { - type T<> = A; + impl Mirror for A { + type T = A; } - struct LocalType<> {} + struct LocalType {} - trait LocalTrait<> { } + trait LocalTrait { } - impl LocalTrait<> for T where T: Iterator<> { } + impl LocalTrait for T where T: Iterator { } - impl<> LocalTrait<> for ::T { } + impl LocalTrait for ::T { } ADDITIONAL } @@ -57,7 +57,7 @@ fn test_overlap_normalize_alias_to_LocalType() { ) "#]] .assert_debug_eq(&test_program_ok(&gen_program( - "impl<> Iterator<> for LocalType<> {}", + "impl Iterator for LocalType {}", ))); } @@ -69,24 +69,24 @@ fn test_overlap_alias_not_normalizable() { let gen_program = |addl: &str| { const BASE_PROGRAM: &str = "[ crate core { - trait Iterator<> { + trait Iterator { } - trait Mirror<> { - type T<> : []; + trait Mirror { + type T : []; } - impl Mirror<> for A { - type T<> = A; + impl Mirror for A { + type T = A; } - struct LocalType<> {} + struct LocalType {} - trait LocalTrait<> { } + trait LocalTrait { } - impl LocalTrait<> for T where T: Iterator<> { } + impl LocalTrait for T where T: Iterator { } - impl LocalTrait<> for ::T where T: Mirror<> { } + impl LocalTrait for ::T where T: Mirror { } ADDITIONAL } @@ -118,6 +118,6 @@ fn test_overlap_alias_not_normalizable() { ) "#]] // FIXME .assert_debug_eq(&test_program_ok(&gen_program( - "impl<> Iterator<> for u32 {}", + "impl Iterator for u32 {}", ))); } diff --git a/tests/projection.rs b/tests/projection.rs index c4fce93a..bf1d6c6e 100644 --- a/tests/projection.rs +++ b/tests/projection.rs @@ -2,16 +2,16 @@ use a_mir_formality::test_where_clause; const NORMALIZE_BASIC: &str = "[ crate test { - trait Iterator<> { - type Item<> : []; + trait Iterator { + type Item : []; } struct Vec {} - struct Foo<> {} + struct Foo {} - impl Iterator<> for Vec { - type Item<> = T; + impl Iterator for Vec { + type Item = T; } } ]"; @@ -52,7 +52,7 @@ fn normalize_basic() { "#]] .assert_debug_eq(&test_where_clause( NORMALIZE_BASIC, - "forall exists {} => { as Iterator>::Item<> = U }", + "forall exists {} => { as Iterator>::Item = U }", )); expect_test::expect![[r#" @@ -73,7 +73,7 @@ fn normalize_basic() { "#]] .assert_debug_eq(&test_where_clause( NORMALIZE_BASIC, - "forall {} => { Iterator(Vec), as Iterator<>>::Item<> = T }", + "forall {} => { Iterator(Vec), as Iterator>::Item = T }", )); expect_test::expect![[r#" @@ -94,7 +94,7 @@ fn normalize_basic() { "#]] .assert_debug_eq(&test_where_clause( NORMALIZE_BASIC, - "forall { Iterator(T), >::Item<> = Foo } => { >::Item<> = Foo }", + "forall { Iterator(T), ::Item = Foo } => { ::Item = Foo }", )); expect_test::expect![[r#" @@ -118,7 +118,7 @@ fn normalize_basic() { "#]] .assert_debug_eq(&test_where_clause( NORMALIZE_BASIC, - "forall exists { Iterator(T) } => { >::Item<> = U }", + "forall exists { Iterator(T) } => { ::Item = U }", )); expect_test::expect![[r#" @@ -139,7 +139,7 @@ fn normalize_basic() { "#]] .assert_debug_eq(&test_where_clause( NORMALIZE_BASIC, - "forall { Iterator(T) } => { >::Item<> = >::Item<> }", + "forall { Iterator(T) } => { ::Item = ::Item }", )); expect_test::expect![[r#" @@ -178,30 +178,30 @@ fn normalize_basic() { "#]] .assert_debug_eq(&test_where_clause( NORMALIZE_BASIC, - "forall exists { Iterator(T) } => { >::Item<> = >::Item<> }", + "forall exists { Iterator(T) } => { ::Item = ::Item }", )); } const NORMALIZE_INTO_ITERATOR: &str = "[ crate test { - trait IntoIterator<> { - type Item<> : []; + trait IntoIterator { + type Item : []; } - trait Iterator<> { - type Item<> : []; + trait Iterator { + type Item : []; } struct Vec {} - struct Foo<> {} + struct Foo {} - impl IntoIterator<> for Vec { - type Item<> = T; + impl IntoIterator for Vec { + type Item = T; } - impl IntoIterator<> for T where T: Iterator<> { - type Item<> = ::Item<>; + impl IntoIterator for T where T: Iterator { + type Item = ::Item; } } ]"; @@ -242,20 +242,20 @@ fn normalize_into_iterator() { "#]] .assert_debug_eq(&test_where_clause( NORMALIZE_INTO_ITERATOR, - "forall exists {} => { as IntoIterator>::Item<> = U }", + "forall exists {} => { as IntoIterator>::Item = U }", )); } const PROJECTION_EQUALITY: &str = "[ crate test { trait Trait1<> { - type Type<> : []; + type Type : []; } trait Trait2 {} impl Trait2 for U where U: Trait1<>, ::Type => T {} - struct S<> {} - impl<> Trait1<> for S<> { - type Type<> = u32; + struct S {} + impl Trait1<> for S { + type Type = u32; } } ]"; @@ -294,7 +294,7 @@ fn projection_equality() { "#]] .assert_debug_eq(&test_where_clause( PROJECTION_EQUALITY, - "exists {} => { Trait1(S), >::Type<> = U }", + "exists {} => { Trait1(S), >::Type = U }", )); expect_test::expect![[r#" diff --git "a/tests/ui/basic_where_clauses_fail.\360\237\224\254" "b/tests/ui/basic_where_clauses_fail.\360\237\224\254" index 7b38d9a3..93c779fb 100644 --- "a/tests/ui/basic_where_clauses_fail.\360\237\224\254" +++ "b/tests/ui/basic_where_clauses_fail.\360\237\224\254" @@ -1,9 +1,9 @@ [ crate core { - trait A where T: B<> { } + trait A where T: B { } - trait B<> { } + trait B { } - trait WellFormed<> where for u32: A { } + trait WellFormed where for u32: A { } } ] diff --git "a/tests/ui/basic_where_clauses_pass.\360\237\224\254" "b/tests/ui/basic_where_clauses_pass.\360\237\224\254" index a8f83e94..b30eadcd 100644 --- "a/tests/ui/basic_where_clauses_pass.\360\237\224\254" +++ "b/tests/ui/basic_where_clauses_pass.\360\237\224\254" @@ -1,12 +1,12 @@ //@check-pass [ crate core { - trait A where T: B<> { } + trait A where T: B { } - trait B<> { } + trait B { } - trait WellFormed<> where for u32: A { } + trait WellFormed where for u32: A { } - impl B<> for T {} + impl B for T {} } ] diff --git "a/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.\360\237\224\254" "b/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.\360\237\224\254" index 3c59770d..7700102d 100644 --- "a/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.\360\237\224\254" +++ "b/tests/ui/coherence_orphan/CoreTrait_for_CoreStruct_in_Foo.\360\237\224\254" @@ -1,9 +1,9 @@ [ crate core { - trait CoreTrait<> {} - struct CoreStruct<> {} + trait CoreTrait {} + struct CoreStruct {} }, crate foo { - impl<> CoreTrait<> for CoreStruct<> {} + impl CoreTrait for CoreStruct {} } ] diff --git "a/tests/ui/coherence_orphan/alias_to_unit.\360\237\224\254" "b/tests/ui/coherence_orphan/alias_to_unit.\360\237\224\254" index 1ff15ea5..31bb5744 100644 --- "a/tests/ui/coherence_orphan/alias_to_unit.\360\237\224\254" +++ "b/tests/ui/coherence_orphan/alias_to_unit.\360\237\224\254" @@ -1,17 +1,17 @@ [ crate core { - trait CoreTrait<> {} + trait CoreTrait {} - trait Unit<> { - type Assoc<> : []; + trait Unit { + type Assoc : []; } - impl Unit<> for T { - type Assoc<> = (); + impl Unit for T { + type Assoc = (); } }, crate foo { - struct FooStruct<> {} - impl<> CoreTrait<> for as Unit<>>::Assoc<> {} + struct FooStruct {} + impl CoreTrait for ::Assoc {} } ] diff --git "a/tests/ui/coherence_orphan/covered_VecT.\360\237\224\254" "b/tests/ui/coherence_orphan/covered_VecT.\360\237\224\254" index bdfe4c46..b054d6d7 100644 --- "a/tests/ui/coherence_orphan/covered_VecT.\360\237\224\254" +++ "b/tests/ui/coherence_orphan/covered_VecT.\360\237\224\254" @@ -5,7 +5,7 @@ struct Vec {} }, crate foo { - struct FooStruct<> {} - impl CoreTrait> for Vec {} + struct FooStruct {} + impl CoreTrait for Vec {} } ] diff --git "a/tests/ui/coherence_orphan/mirror_CoreStruct.\360\237\224\254" "b/tests/ui/coherence_orphan/mirror_CoreStruct.\360\237\224\254" index 96cb16fd..b6ebb5e8 100644 --- "a/tests/ui/coherence_orphan/mirror_CoreStruct.\360\237\224\254" +++ "b/tests/ui/coherence_orphan/mirror_CoreStruct.\360\237\224\254" @@ -1,17 +1,17 @@ [ crate core { - trait CoreTrait<> {} - struct CoreStruct<> {} + trait CoreTrait {} + struct CoreStruct {} - trait Mirror<> { - type Assoc<> : []; + trait Mirror { + type Assoc : []; } - impl Mirror<> for T { - type Assoc<> = T; + impl Mirror for T { + type Assoc = T; } }, crate foo { - impl<> CoreTrait<> for as Mirror<>>::Assoc<> {} + impl CoreTrait for ::Assoc {} } ] diff --git "a/tests/ui/coherence_orphan/mirror_FooStruct.\360\237\224\254" "b/tests/ui/coherence_orphan/mirror_FooStruct.\360\237\224\254" index f8cdd8b1..d92ab188 100644 --- "a/tests/ui/coherence_orphan/mirror_FooStruct.\360\237\224\254" +++ "b/tests/ui/coherence_orphan/mirror_FooStruct.\360\237\224\254" @@ -1,18 +1,18 @@ //@check-pass [ crate core { - trait CoreTrait<> {} + trait CoreTrait {} - trait Mirror<> { - type Assoc<> : []; + trait Mirror { + type Assoc : []; } - impl Mirror<> for T { - type Assoc<> = T; + impl Mirror for T { + type Assoc = T; } }, crate foo { - struct FooStruct<> {} - impl<> CoreTrait<> for as Mirror<>>::Assoc<> {} + struct FooStruct {} + impl CoreTrait for ::Assoc {} } ] diff --git "a/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.\360\237\224\254" "b/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.\360\237\224\254" index ac962247..208512f7 100644 --- "a/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.\360\237\224\254" +++ "b/tests/ui/coherence_orphan/neg_CoreTrait_for_CoreStruct_in_Foo.\360\237\224\254" @@ -1,9 +1,9 @@ [ crate core { - trait CoreTrait<> {} - struct CoreStruct<> {} + trait CoreTrait {} + struct CoreStruct {} }, crate foo { - impl<> !CoreTrait<> for CoreStruct<> {} + impl !CoreTrait for CoreStruct {} } ] diff --git "a/tests/ui/coherence_orphan/uncovered_T.\360\237\224\254" "b/tests/ui/coherence_orphan/uncovered_T.\360\237\224\254" index 7f94d974..6082f558 100644 --- "a/tests/ui/coherence_orphan/uncovered_T.\360\237\224\254" +++ "b/tests/ui/coherence_orphan/uncovered_T.\360\237\224\254" @@ -3,7 +3,7 @@ trait CoreTrait {} }, crate foo { - struct FooStruct<> {} - impl CoreTrait> for T {} + struct FooStruct {} + impl CoreTrait for T {} } ] diff --git "a/tests/ui/coherence_overlap/T_where_Foo_not_u32_impls.\360\237\224\254" "b/tests/ui/coherence_overlap/T_where_Foo_not_u32_impls.\360\237\224\254" index fb034f80..9e3c2db0 100644 --- "a/tests/ui/coherence_overlap/T_where_Foo_not_u32_impls.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/T_where_Foo_not_u32_impls.\360\237\224\254" @@ -5,8 +5,8 @@ // was erroneously accepted by an earlier variant of negative impls. [ crate core { - trait Foo<> {} - impl Foo<> for T where T: Foo<> {} - impl<> !Foo<> for u32 {} + trait Foo {} + impl Foo for T where T: Foo {} + impl !Foo for u32 {} } ] diff --git "a/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.\360\237\224\254" "b/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.\360\237\224\254" index f908376e..7331d51f 100644 --- "a/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait.\360\237\224\254" @@ -1,11 +1,11 @@ [ crate core { - trait CoreTrait<> {} - struct CoreStruct<> {} + trait CoreTrait {} + struct CoreStruct {} }, crate foo { - trait FooTrait<> {} - impl FooTrait<> for T where T: CoreTrait<> {} - impl<> FooTrait<> for CoreStruct<> {} + trait FooTrait {} + impl FooTrait for T where T: CoreTrait {} + impl FooTrait for CoreStruct {} } ] diff --git "a/tests/ui/coherence_overlap/neg_CoreTrait_for_CoreStruct_implies_no_overlap.\360\237\224\254" "b/tests/ui/coherence_overlap/neg_CoreTrait_for_CoreStruct_implies_no_overlap.\360\237\224\254" index c87ce1cf..632420b4 100644 --- "a/tests/ui/coherence_overlap/neg_CoreTrait_for_CoreStruct_implies_no_overlap.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/neg_CoreTrait_for_CoreStruct_implies_no_overlap.\360\237\224\254" @@ -3,13 +3,13 @@ // where there is a negative impl, so it is accepted. [ crate core { - trait CoreTrait<> {} - struct CoreStruct<> {} - impl<> !CoreTrait<> for CoreStruct<> {} + trait CoreTrait {} + struct CoreStruct {} + impl !CoreTrait for CoreStruct {} }, crate foo { - trait FooTrait<> {} - impl FooTrait<> for T where T: CoreTrait<> {} - impl<> FooTrait<> for CoreStruct<> {} + trait FooTrait {} + impl FooTrait for T where T: CoreTrait {} + impl FooTrait for CoreStruct {} } ] diff --git "a/tests/ui/coherence_overlap/u32_T_impls.\360\237\224\254" "b/tests/ui/coherence_overlap/u32_T_impls.\360\237\224\254" index 9b0afc3e..ebd0cf4d 100644 --- "a/tests/ui/coherence_overlap/u32_T_impls.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/u32_T_impls.\360\237\224\254" @@ -1,7 +1,7 @@ [ crate core { - trait Foo<> {} - impl<> Foo<> for u32 {} - impl Foo<> for T {} + trait Foo {} + impl Foo for u32 {} + impl Foo for T {} } ] diff --git "a/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.\360\237\224\254" "b/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.\360\237\224\254" index 9f289098..c0abce62 100644 --- "a/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/u32_T_where_T_Is_impls.\360\237\224\254" @@ -2,11 +2,11 @@ // and also all `T: Is`, and `u32: Is`. [ crate core { - trait Foo<> {} - impl<> Foo<> for u32 {} - impl Foo<> for T where T: Is<> {} + trait Foo {} + impl Foo for u32 {} + impl Foo for T where T: Is {} - trait Is<> {} - impl<> Is<> for u32 {} + trait Is {} + impl Is for u32 {} } ] diff --git "a/tests/ui/coherence_overlap/u32_T_where_T_Not_impls.\360\237\224\254" "b/tests/ui/coherence_overlap/u32_T_where_T_Not_impls.\360\237\224\254" index eba474e6..ce525162 100644 --- "a/tests/ui/coherence_overlap/u32_T_where_T_Not_impls.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/u32_T_where_T_Not_impls.\360\237\224\254" @@ -6,10 +6,10 @@ // See also test_foo_crate_cannot_assume_CoreStruct_does_not_impl_CoreTrait [ crate core { - trait Foo<> {} - impl<> Foo<> for u32 {} - impl Foo<> for T where T: Not<> {} + trait Foo {} + impl Foo for u32 {} + impl Foo for T where T: Not {} - trait Not<> {} + trait Not {} } ] diff --git "a/tests/ui/coherence_overlap/u32_i32_impls.\360\237\224\254" "b/tests/ui/coherence_overlap/u32_i32_impls.\360\237\224\254" index 4e6c14da..e2fbc191 100644 --- "a/tests/ui/coherence_overlap/u32_i32_impls.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/u32_i32_impls.\360\237\224\254" @@ -1,8 +1,8 @@ //@check-pass [ crate core { - trait Foo<> {} - impl<> Foo<> for u32 {} - impl<> Foo<> for i32 {} + trait Foo {} + impl Foo for u32 {} + impl Foo for i32 {} } ] diff --git "a/tests/ui/coherence_overlap/u32_not_u32_impls.\360\237\224\254" "b/tests/ui/coherence_overlap/u32_not_u32_impls.\360\237\224\254" index 6dfae3d7..941b8550 100644 --- "a/tests/ui/coherence_overlap/u32_not_u32_impls.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/u32_not_u32_impls.\360\237\224\254" @@ -1,8 +1,8 @@ // Test that a positive and negative impl for the same type (`u32`, here) is rejected. [ crate core { - trait Foo<> {} - impl<> Foo<> for u32 {} - impl<> !Foo<> for u32 {} + trait Foo {} + impl Foo for u32 {} + impl !Foo for u32 {} } ] diff --git "a/tests/ui/coherence_overlap/u32_u32_impls.\360\237\224\254" "b/tests/ui/coherence_overlap/u32_u32_impls.\360\237\224\254" index e61914c5..59069e29 100644 --- "a/tests/ui/coherence_overlap/u32_u32_impls.\360\237\224\254" +++ "b/tests/ui/coherence_overlap/u32_u32_impls.\360\237\224\254" @@ -1,7 +1,7 @@ [ crate core { - trait Foo<> {} - impl<> Foo<> for u32 {} - impl<> Foo<> for u32 {} + trait Foo {} + impl Foo for u32 {} + impl Foo for u32 {} } ] diff --git "a/tests/ui/consts/holds.\360\237\224\254" "b/tests/ui/consts/holds.\360\237\224\254" index 7a52aafd..c1ac5ca2 100644 --- "a/tests/ui/consts/holds.\360\237\224\254" +++ "b/tests/ui/consts/holds.\360\237\224\254" @@ -3,6 +3,6 @@ crate Foo { trait Foo where type_of_const C is bool {} - impl<> Foo for u32 {} + impl Foo for u32 {} } ] diff --git "a/tests/ui/consts/mismatch.\360\237\224\254" "b/tests/ui/consts/mismatch.\360\237\224\254" index 901bbad8..d3aad171 100644 --- "a/tests/ui/consts/mismatch.\360\237\224\254" +++ "b/tests/ui/consts/mismatch.\360\237\224\254" @@ -2,6 +2,6 @@ crate Foo { trait Foo where type_of_const C is bool {} - impl<> Foo for u32 {} + impl Foo for u32 {} } ] diff --git "a/tests/ui/consts/nonsense_rigid_const_bound.\360\237\224\254" "b/tests/ui/consts/nonsense_rigid_const_bound.\360\237\224\254" index 26824392..d3d0f2bb 100644 --- "a/tests/ui/consts/nonsense_rigid_const_bound.\360\237\224\254" +++ "b/tests/ui/consts/nonsense_rigid_const_bound.\360\237\224\254" @@ -2,6 +2,6 @@ /// substituting and directly going to a wrong constant. [ crate Foo { - trait Foo<> where type_of_const true is u32 {} + trait Foo where type_of_const true is u32 {} } ] diff --git "a/tests/ui/consts/rigid_const_bound.\360\237\224\254" "b/tests/ui/consts/rigid_const_bound.\360\237\224\254" index 210cb9db..4a0856ff 100644 --- "a/tests/ui/consts/rigid_const_bound.\360\237\224\254" +++ "b/tests/ui/consts/rigid_const_bound.\360\237\224\254" @@ -3,6 +3,6 @@ //@check-pass [ crate Foo { - trait Foo<> where type_of_const true is bool {} + trait Foo where type_of_const true is bool {} } ] diff --git "a/tests/ui/fn/ok.\360\237\224\254" "b/tests/ui/fn/ok.\360\237\224\254" index 57925027..8925cf45 100644 --- "a/tests/ui/fn/ok.\360\237\224\254" +++ "b/tests/ui/fn/ok.\360\237\224\254" @@ -3,7 +3,7 @@ [ crate Foo { // fn simple_fn() {} - fn simple_fn<>() -> () { trusted } + fn simple_fn() -> () { trusted } // fn one_arg(_: T) {} fn one_arg(T) -> () { trusted } diff --git "a/tests/ui/hello_world.\360\237\224\254" "b/tests/ui/hello_world.\360\237\224\254" index fd0c81b6..6b035c1e 100644 --- "a/tests/ui/hello_world.\360\237\224\254" +++ "b/tests/ui/hello_world.\360\237\224\254" @@ -1,15 +1,15 @@ //@check-pass [ crate Foo { - trait Foo where T: Bar, Self: Baz<> {} + trait Foo where T: Bar, Self: Baz {} - trait Bar where T: Baz<> {} + trait Bar where T: Baz {} - trait Baz<> {} + trait Baz {} - impl<> Baz<> for u32 {} + impl Baz for u32 {} - impl<> Bar for u32 {} - impl Bar for () where T: Baz<> {} + impl Bar for u32 {} + impl Bar for () where T: Baz {} } ] diff --git "a/tests/ui/hello_world_fail.\360\237\224\254" "b/tests/ui/hello_world_fail.\360\237\224\254" index f6709f1d..69ca1485 100644 --- "a/tests/ui/hello_world_fail.\360\237\224\254" +++ "b/tests/ui/hello_world_fail.\360\237\224\254" @@ -2,8 +2,8 @@ crate Foo { trait Foo where T: Bar {} - trait Bar where T: Baz<> {} + trait Bar where T: Baz {} - trait Baz<> {} + trait Baz {} } ] diff --git a/tests/ui/parser.stderr b/tests/ui/parser.stderr index a9017fad..5b943336 100644 --- a/tests/ui/parser.stderr +++ b/tests/ui/parser.stderr @@ -7,7 +7,7 @@ Caused by: 1: failed to parse [ crate Foo { - trait Baz<> where cake {} + trait Baz where cake {} } ] diff --git "a/tests/ui/parser.\360\237\224\254" "b/tests/ui/parser.\360\237\224\254" index 273407a4..d2e35fb2 100644 --- "a/tests/ui/parser.\360\237\224\254" +++ "b/tests/ui/parser.\360\237\224\254" @@ -1,5 +1,5 @@ [ crate Foo { - trait Baz<> where cake {} + trait Baz where cake {} } ] From af41c9a3cfda23731c2982ab69273fb3004d02de Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 5 Nov 2023 05:49:21 -0500 Subject: [PATCH 16/38] refactor Parser API to use `multi_variant` This will make it easier to support fixed point parsing. --- crates/formality-core/src/parse/parser.rs | 29 ++- crates/formality-macros/src/parse.rs | 6 +- .../src/grammar/ty/parse_impls.rs | 186 +++++++++--------- 3 files changed, 113 insertions(+), 108 deletions(-) diff --git a/crates/formality-core/src/parse/parser.rs b/crates/formality-core/src/parse/parser.rs index a8618cec..387612aa 100644 --- a/crates/formality-core/src/parse/parser.rs +++ b/crates/formality-core/src/parse/parser.rs @@ -73,16 +73,23 @@ where nonterminal_name: &'static str, op: impl FnOnce(&mut ActiveVariant<'s, 't, L>) -> Result>>, ) -> ParseResult<'t, T> { - let mut result = Parser::new(scope, text, nonterminal_name); - result.parse_variant(nonterminal_name, 0, op); - result.finish() + Parser::multi_variant(scope, text, nonterminal_name, |parser| { + parser.parse_variant(nonterminal_name, 0, op); + }) } - /// Creates a new parser. You should then invoke `parse_variant` 0 or more times for - /// each of the possibilities and finally invoke `finish`. + /// Creates a new parser that can accommodate multiple variants. + /// + /// Invokes `op` to do the parsing; `op` should call `parse_variant` 0 or more times for + /// each of the possibilities. The best result (if any) will then be returned. /// /// The method [`single_variant`] is more convenient if you have exactly one variant. - pub fn new(scope: &'s Scope, text: &'t str, nonterminal_name: &'static str) -> Self { + pub fn multi_variant( + scope: &'s Scope, + text: &'t str, + nonterminal_name: &'static str, + op: impl FnOnce(&mut Self), + ) -> ParseResult<'t, T> { let tracing_span = tracing::span!( tracing::Level::TRACE, "nonterminal", @@ -92,14 +99,18 @@ where ) .entered(); - Self { + let mut parser = Self { scope, start_text: text, nonterminal_name, tracing_span, successes: vec![], failures: set![], - } + }; + + op(&mut parser); + + parser.finish() } /// Shorthand for `parse_variant` where the parsing operation is to @@ -173,7 +184,7 @@ where } } - pub fn finish(self) -> ParseResult<'t, T> { + fn finish(self) -> ParseResult<'t, T> { // If we did not parse anything successfully, then return an error. // There are two possibilities: some of our variants may have made // progress but ultimately failed. If so, self.failures will be non-empty, diff --git a/crates/formality-macros/src/parse.rs b/crates/formality-macros/src/parse.rs index 7b0d4f61..42aa2785 100644 --- a/crates/formality-macros/src/parse.rs +++ b/crates/formality-macros/src/parse.rs @@ -55,9 +55,9 @@ pub(crate) fn derive_parse_with_spec( gen impl parse::CoreParse for @Self { fn parse<'t>(scope: &parse::Scope, text: &'t str) -> parse::ParseResult<'t, Self> { - let mut __parser = parse::Parser::new(scope, text, #type_name); - #parse_variants; - __parser.finish() + parse::Parser::multi_variant(scope, text, #type_name, |__parser| { + #parse_variants; + }) } } })) diff --git a/crates/formality-types/src/grammar/ty/parse_impls.rs b/crates/formality-types/src/grammar/ty/parse_impls.rs index 4765ac1a..24db70e3 100644 --- a/crates/formality-types/src/grammar/ty/parse_impls.rs +++ b/crates/formality-types/src/grammar/ty/parse_impls.rs @@ -16,94 +16,90 @@ use crate::rust::FormalityLang as Rust; // Implement custom parsing for rigid types. impl CoreParse for RigidTy { fn parse<'t>(scope: &Scope, text: &'t str) -> ParseResult<'t, Self> { - let mut parser: Parser<'_, '_, RigidTy, Rust> = Parser::new(scope, text, "AliasTy"); - - // Parse a `ScalarId` (and upcast it to `RigidTy`) with the highest - // precedence. If someone writes `u8`, we always interpret it as a - // scalar-id. - parser.parse_variant_cast::(1); - - // Parse something like `Id<...>` as an ADT. - parser.parse_variant("Adt", 0, |p| { - let name: AdtId = p.nonterminal()?; - let parameters: Vec = parse_parameters(p)?; - Ok(RigidTy { - name: name.upcast(), - parameters, - }) - }); - - // Parse `&` - parser.parse_variant("Ref", 0, |p| { - p.expect_char('&')?; - let lt: Lt = p.nonterminal()?; - let ty: Ty = p.nonterminal()?; - Ok(RigidTy { - name: RigidName::Ref(RefKind::Shared), - parameters: seq![lt.upcast(), ty.upcast()], - } - .upcast()) - }); - - parser.parse_variant("RefMut", 0, |p| { - p.expect_char('&')?; - p.expect_keyword("mut")?; - let lt: Lt = p.nonterminal()?; - let ty: Ty = p.nonterminal()?; - Ok(RigidTy { - name: RigidName::Ref(RefKind::Mut), - parameters: seq![lt.upcast(), ty.upcast()], - }) - }); - - parser.parse_variant("Tuple", 0, |p| { - p.expect_char('(')?; - p.reject_custom_keywords(&["alias", "rigid", "predicate"])?; - let types: Vec = p.comma_nonterminal()?; - p.expect_char(')')?; - let name = RigidName::Tuple(types.len()); - Ok(RigidTy { - name, - parameters: types.upcast(), - }) - }); - - parser.finish() + Parser::multi_variant(scope, text, "AliasTy", |parser| { + // Parse a `ScalarId` (and upcast it to `RigidTy`) with the highest + // precedence. If someone writes `u8`, we always interpret it as a + // scalar-id. + parser.parse_variant_cast::(1); + + // Parse something like `Id<...>` as an ADT. + parser.parse_variant("Adt", 0, |p| { + let name: AdtId = p.nonterminal()?; + let parameters: Vec = parse_parameters(p)?; + Ok(RigidTy { + name: name.upcast(), + parameters, + }) + }); + + // Parse `&` + parser.parse_variant("Ref", 0, |p| { + p.expect_char('&')?; + let lt: Lt = p.nonterminal()?; + let ty: Ty = p.nonterminal()?; + Ok(RigidTy { + name: RigidName::Ref(RefKind::Shared), + parameters: seq![lt.upcast(), ty.upcast()], + } + .upcast()) + }); + + parser.parse_variant("RefMut", 0, |p| { + p.expect_char('&')?; + p.expect_keyword("mut")?; + let lt: Lt = p.nonterminal()?; + let ty: Ty = p.nonterminal()?; + Ok(RigidTy { + name: RigidName::Ref(RefKind::Mut), + parameters: seq![lt.upcast(), ty.upcast()], + }) + }); + + parser.parse_variant("Tuple", 0, |p| { + p.expect_char('(')?; + p.reject_custom_keywords(&["alias", "rigid", "predicate"])?; + let types: Vec = p.comma_nonterminal()?; + p.expect_char(')')?; + let name = RigidName::Tuple(types.len()); + Ok(RigidTy { + name, + parameters: types.upcast(), + }) + }); + }) } } // ANCHOR_END: RigidTy_impl impl CoreParse for AliasTy { fn parse<'t>(scope: &Scope, text: &'t str) -> ParseResult<'t, Self> { - let mut parser: Parser<'_, '_, AliasTy, Rust> = Parser::new(scope, text, "AliasTy"); - - parser.parse_variant("associated type", 0, |p| { - p.expect_char('<')?; - let ty0: Ty = p.nonterminal()?; - let () = p.expect_keyword("as")?; - let trait_id: TraitId = p.nonterminal()?; - let trait_parameters1 = parse_parameters(p)?; - p.expect_char('>')?; - p.expect_char(':')?; - p.expect_char(':')?; - let item_id: AssociatedItemId = p.nonterminal()?; - let item_parameters = parse_parameters(p)?; - let name = AssociatedTyName { - trait_id, - item_id, - item_arity: item_parameters.len(), - }; - let parameters: Vec = std::iter::once(ty0.upcast()) - .chain(trait_parameters1) - .chain(item_parameters) - .collect(); - Ok(AliasTy { - name: name.upcast(), - parameters, - }) - }); - - parser.finish() + Parser::multi_variant(scope, text, "AliasTy", |parser| { + parser.parse_variant("associated type", 0, |p| { + p.expect_char('<')?; + let ty0: Ty = p.nonterminal()?; + let () = p.expect_keyword("as")?; + let trait_id: TraitId = p.nonterminal()?; + let trait_parameters1 = parse_parameters(p)?; + p.expect_char('>')?; + p.expect_char(':')?; + p.expect_char(':')?; + let item_id: AssociatedItemId = p.nonterminal()?; + let item_parameters = parse_parameters(p)?; + let name = AssociatedTyName { + trait_id, + item_id, + item_arity: item_parameters.len(), + }; + let parameters: Vec = std::iter::once(ty0.upcast()) + .chain(trait_parameters1) + .chain(item_parameters) + .collect(); + Ok(AliasTy { + name: name.upcast(), + parameters, + }) + }); + }) } } @@ -122,19 +118,17 @@ fn parse_parameters<'t>( // writing tests so much more pleasant. impl CoreParse for ConstData { fn parse<'t>(scope: &Scope, text: &'t str) -> ParseResult<'t, Self> { - let mut parser: Parser<'_, '_, ConstData, Rust> = Parser::new(scope, text, "Ty"); - - parser.parse_variant("Variable", 1, |p| p.variable()); - - parser.parse_variant_cast::(1); - - parser.parse_variant("Int", 0, |p| { - let n: u128 = p.number()?; - p.expect_char('_')?; - let ty: Ty = p.nonterminal()?; - Ok(ConstData::Value(Scalar::new(n).upcast(), ty)) - }); - - parser.finish() + Parser::multi_variant(scope, text, "ConstData", |parser| { + parser.parse_variant("Variable", 1, |p| p.variable()); + + parser.parse_variant_cast::(1); + + parser.parse_variant("Int", 0, |p| { + let n: u128 = p.number()?; + p.expect_char('_')?; + let ty: Ty = p.nonterminal()?; + Ok(ConstData::Value(Scalar::new(n).upcast(), ty)) + }); + }) } } From 2dc6a9754c2bd7235f8ec16a0f564ed338b91139 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 5 Nov 2023 05:55:05 -0500 Subject: [PATCH 17/38] refactor tracing for clearer printouts --- crates/formality-core/src/parse/parser.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/formality-core/src/parse/parser.rs b/crates/formality-core/src/parse/parser.rs index 387612aa..c130ad81 100644 --- a/crates/formality-core/src/parse/parser.rs +++ b/crates/formality-core/src/parse/parser.rs @@ -24,8 +24,6 @@ where { scope: &'s Scope, start_text: &'t str, - #[allow(dead_code)] - tracing_span: tracing::span::EnteredSpan, nonterminal_name: &'static str, successes: Vec<(SuccessfulParse<'t, T>, Precedence)>, failures: Set>, @@ -96,21 +94,20 @@ where name = nonterminal_name, ?scope, ?text - ) - .entered(); + ); + let guard = tracing_span.enter(); let mut parser = Self { scope, start_text: text, nonterminal_name, - tracing_span, successes: vec![], failures: set![], }; op(&mut parser); - parser.finish() + parser.finish(guard) } /// Shorthand for `parse_variant` where the parsing operation is to @@ -184,7 +181,7 @@ where } } - fn finish(self) -> ParseResult<'t, T> { + fn finish(self, guard: tracing::span::Entered<'_>) -> ParseResult<'t, T> { // If we did not parse anything successfully, then return an error. // There are two possibilities: some of our variants may have made // progress but ultimately failed. If so, self.failures will be non-empty, @@ -195,6 +192,8 @@ where // observe that we did not consume any tokens and will ignore our messawge // and put its own (e.g., "failed to find a Y here"). if self.successes.is_empty() { + // It's better to print this result alongside the main parsing section. + drop(guard); return if self.failures.is_empty() { tracing::trace!("parsing failed: no variants were able to consume a single token"); Err(ParseError::at( @@ -228,6 +227,8 @@ where .all(|(s_j, j)| i == j || Self::is_preferable(s_i, s_j)) { let (s_i, _) = self.successes.into_iter().skip(i).next().unwrap(); + // It's better to print this result alongside the main parsing section. + drop(guard); tracing::trace!("best parse = `{:?}`", s_i); return Ok(s_i); } From 1df4375b82757635869dac1f61c8501290086e53 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 5 Nov 2023 07:18:37 -0500 Subject: [PATCH 18/38] introduce left-recursive parsing --- crates/formality-core/src/parse.rs | 4 +- crates/formality-core/src/parse/parser.rs | 49 ++-- .../src/parse/parser/left_recursion.rs | 243 ++++++++++++++++++ examples/formality-eg/grammar.rs | 3 + examples/formality-eg/grammar/test.rs | 12 +- 5 files changed, 286 insertions(+), 25 deletions(-) create mode 100644 crates/formality-core/src/parse/parser/left_recursion.rs diff --git a/crates/formality-core/src/parse.rs b/crates/formality-core/src/parse.rs index 7ae0aa68..363729c4 100644 --- a/crates/formality-core/src/parse.rs +++ b/crates/formality-core/src/parse.rs @@ -14,7 +14,7 @@ use std::fmt::Debug; /// Trait for parsing a [`Term`](`crate::term::Term`) as input. /// Typically this is auto-generated with the `#[term]` procedural macro, /// but you can implement it by hand if you want a very customized parse. -pub trait CoreParse: Sized + Debug + Clone + Eq { +pub trait CoreParse: Sized + Debug + Clone + Eq + 'static { /// Parse a single instance of this type, returning an error if no such /// instance is present. /// @@ -58,7 +58,7 @@ where } /// Record from a successful parse. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct SuccessfulParse<'t, T> { /// The new point in the input, after we've consumed whatever text we have. text: &'t str, diff --git a/crates/formality-core/src/parse/parser.rs b/crates/formality-core/src/parse/parser.rs index c130ad81..1cd96275 100644 --- a/crates/formality-core/src/parse/parser.rs +++ b/crates/formality-core/src/parse/parser.rs @@ -8,6 +8,8 @@ use crate::{ use super::{CoreParse, ParseError, ParseResult, Scope, SuccessfulParse, TokenResult}; +mod left_recursion; + /// Create this struct when implementing the [`CoreParse`][] trait. /// Each `Parser` corresponds to some symbol in the grammar. /// You create a parser and then you invoke the `parse_variant` @@ -21,6 +23,7 @@ use super::{CoreParse, ParseError, ParseResult, Scope, SuccessfulParse, TokenRes pub struct Parser<'s, 't, T, L> where L: Language, + T: Debug + Clone + Eq + 'static, { scope: &'s Scope, start_text: &'t str, @@ -59,7 +62,7 @@ where impl<'s, 't, T, L> Parser<'s, 't, T, L> where L: Language, - T: Debug + Eq, + T: Debug + Clone + Eq + 'static, { /// Shorthand to create a parser for a nonterminal with a single variant, /// parsed by the function `op`. @@ -69,10 +72,10 @@ where scope: &'s Scope, text: &'t str, nonterminal_name: &'static str, - op: impl FnOnce(&mut ActiveVariant<'s, 't, L>) -> Result>>, + mut op: impl FnMut(&mut ActiveVariant<'s, 't, L>) -> Result>>, ) -> ParseResult<'t, T> { Parser::multi_variant(scope, text, nonterminal_name, |parser| { - parser.parse_variant(nonterminal_name, 0, op); + parser.parse_variant(nonterminal_name, 0, &mut op); }) } @@ -86,28 +89,30 @@ where scope: &'s Scope, text: &'t str, nonterminal_name: &'static str, - op: impl FnOnce(&mut Self), + mut op: impl FnMut(&mut Self), ) -> ParseResult<'t, T> { - let tracing_span = tracing::span!( - tracing::Level::TRACE, - "nonterminal", - name = nonterminal_name, - ?scope, - ?text - ); - let guard = tracing_span.enter(); - - let mut parser = Self { - scope, - start_text: text, - nonterminal_name, - successes: vec![], - failures: set![], - }; + left_recursion::enter(scope, text, || { + let tracing_span = tracing::span!( + tracing::Level::TRACE, + "nonterminal", + name = nonterminal_name, + ?scope, + ?text + ); + let guard = tracing_span.enter(); + + let mut parser = Self { + scope, + start_text: text, + nonterminal_name, + successes: vec![], + failures: set![], + }; - op(&mut parser); + op(&mut parser); - parser.finish(guard) + parser.finish(guard) + }) } /// Shorthand for `parse_variant` where the parsing operation is to diff --git a/crates/formality-core/src/parse/parser/left_recursion.rs b/crates/formality-core/src/parse/parser/left_recursion.rs new file mode 100644 index 00000000..8d781688 --- /dev/null +++ b/crates/formality-core/src/parse/parser/left_recursion.rs @@ -0,0 +1,243 @@ +//! Support left-recursive grammars. This is basically just a fixed point +//! operation, but we re-implement it to avoid having to return multiple +//! success values, since we know that's not really needed here. +//! +//! This unfortunately requires unsafe and even `type_id`. This is because +//! we need to be generic over the language `L` and the result type +//! `T` in our `thread_local!` and you can't have generic thread-local values. +//! So we have to erase types. Annoying! + +use std::{any::TypeId, cell::RefCell, fmt::Debug}; + +use crate::{ + language::Language, + parse::{ParseError, ParseResult, Scope, SuccessfulParse}, +}; + +thread_local! { + static STACK: RefCell> = Default::default() +} + +/// Tracks an active parse that is taking place. +struct StackEntry { + /// The scope pointer: we use `()` instead of `Scope` + scope: *const (), + + /// The starting text: we use `*const` instead of `&'t str` + start_text: *const str, + + /// The TypeId of the type `T`. + type_id: TypeId, + + /// The intermediate value produced. If `Some`, this is a pointer + /// to a `SuccessfulParse<'t, T>`. + value: Option<*const ()>, + + /// + observed: bool, +} + +impl StackEntry { + pub fn new(scope: &Scope, start_text: &str) -> Self + where + L: Language, + T: Clone + 'static, + { + Self { + scope: erase_type(scope), + start_text, + type_id: TypeId::of::(), + value: None, + observed: false, + } + } + + pub fn matches(&self, scope: &Scope, start_text: &str) -> bool + where + L: Language, + T: Clone + 'static, + { + let scope: *const () = erase_type(scope); + let start_text: *const str = start_text; + let type_id = TypeId::of::(); + scope == self.scope && start_text == self.start_text && self.type_id == type_id + } + + /// UNSAFE: Caller must guarantee that `self.value` pointer is valid. + pub unsafe fn observe<'t, T>(&mut self, start_text: &'t str) -> ParseResult<'t, T> + where + T: Clone + 'static, + { + assert_eq!(self.start_text, start_text as *const str); + assert_eq!(self.type_id, TypeId::of::()); + + self.observed = true; + + match self.value { + Some(ptr) => { + let ptr = ptr as *const SuccessfulParse<'t, T>; + // UNSAFE: We rely on the caller to entry ptr is valid. + let ptr = unsafe { &*ptr }; + Ok(ptr.clone()) + } + None => Err(ParseError::at( + start_text, + format!("recursive grammar for `{}`", std::any::type_name::()), + )), + } + } +} + +pub fn enter<'s, 't, L, T>( + scope: &'s Scope, + text: &'t str, + mut op: impl FnMut() -> ParseResult<'t, T>, +) -> ParseResult<'t, T> +where + L: Language, + T: Debug + Clone + Eq + 'static, +{ + tracing::trace!( + "enter<{}>(scope={:?}, text={:?})", + std::any::type_name::(), + scope, + text + ); + + // First check whether we are already parsing this same text in this same scope as this same type. + let previous_result = STACK.with_borrow_mut(|stack| { + if let Some(entry) = stack + .iter_mut() + .find(|entry| entry.matches::(scope, text)) + { + // UNSAFE: We need to justify that `entry.value` will be valid. + // + // Each entry in `stack` corresponds to an active stack frame `F` on this thread + // and each entry in `stack` is only mutated by `F` + // + // The value in `entry.value` will either be `None` (in which case it is valid) + // or `Some(p)` where `p` is a pointer. + // + // `p` will have been assigned by `F` just before invoking `op()`. It is a reference + // to the last value in a vector owned by `F`. Since `F` is still active, that vector + // is still valid. The borrow to produce `p` is valid (by inspection) because there are no + // accesses to the vector until `op` completes + // (and, to arrive at this code, `op` has not yet completed). + unsafe { + let result = entry.observe::(text); + tracing::trace!("found left-recursive stack entry, result = {:?}", result); + Some(result) + } + } else { + stack.push(StackEntry::new::(scope, text)); + None + } + }); + if let Some(previous_result) = previous_result { + return previous_result; + } + + // Access the top stack frame. Use a macro because we don't support closures + // that are generic over the return type. + macro_rules! with_top { + (|$top:ident| $body:expr) => { + STACK.with_borrow_mut(|stack| { + let $top = stack.last_mut().unwrap(); + assert!($top.matches::(scope, text)); + $body + }) + }; + } + + let pop_stack_before_return = |r: ParseResult<'t, T>| { + STACK.with_borrow_mut(|stack| { + let top = stack.pop().unwrap(); + assert!(top.matches::(scope, text)); + }); + r + }; + + // EXAMPLE: Consider this grammar + // + // ``` + // Expr = Expr '+' Expr + // | Integer + // ``` + // + // and this input `2 + 3`. We process this in rounds. + // + // Round 0: Previous value `value` is `None`. When we go to parse expr, it will recurse, + // which will yield an error that consumes zero tokens. We will then attempt integer, + // which succeeds, yielding a parsed result of `2` with remainder `+ 3`. + // + // Round 1: We store `(2, "+ 3")` as the previous result and try again. When we go to parse `Expr`, + // there are two options. First, we successfully parse as an integer just like before. + // But also we are able to parse as `Expr + Expr`, because the left recursive reference to `Expr` yields `2` + // and we can continue and parse `2 + 3`. The `Parser` prefers this longer result and so we get + // `2 + 3` as the final result. + // + // Round 2: We store `(2+3, "")` as the previous result and try again. *This time* when we recurse, + // we get `2` again! The reason why is a bit surprising. The parse of `2` succeeds with remainder + // `"+ 3"`. But when we go parse `Expr + Expr`, the first `Expr` result yields `2 + 3` and there are no more + // tokens, so that arm fails. In our loop below, we search back through the result and find that `2` has already + // occurred, so we take `2 + 3` as the best overall parse. + // + // It's a bit subtle why this is ok. It's relying on some properties of grammars and parsing. + // To be more obviously correct we would want to return sets of successful results. + // In particular, the assumption is that `op` is always returning a best result (if any) and panicking on + // ambiguity. + + // First round parse is a bit special, because if we get an error here, we can just return immediately, + // as there is no base case to build from. + let mut values = vec![]; + match op() { + Ok(v) => values.push(v), + Err(errs) => return pop_stack_before_return(Err(errs)), + }; + + // Check whether there was recursion to begin with. + let observed = with_top!(|top| top.observed); + if !observed { + return pop_stack_before_return(Ok(values.pop().unwrap())); // If not, we are done. + } + + // OK, this is the interesting case. We may be able to get a better parse. + loop { + tracing::trace!( + "reparsing of left-recursive grammar: values = {:#?}", + values + ); + + // If we have an intermediate value, update the stack entry to point at. + // This takes a borrow of `value` but converts it into a raw pointer. + // This borrow lasts until after `op` is complete. + let best_value = values.last().unwrap(); + with_top!(|top| { + top.value = Some(erase_type(best_value)); + }); + + // Invoke the operation. As noted above, if we get a failed parse NOW, + // we know we already found the best result, so we can just use it. + let Ok(value1) = op() else { + return pop_stack_before_return(Ok(values.pop().unwrap())); // If not, we are done. + }; + + tracing::trace!("left-recursive grammar yielded: value1 = {:?}", value1); + + // If we got back on the previous results we saw, then we're entering + // a loop and we can stop and take the best one (which should also be the longest). + // In our example, this occurs when we parse `6` -- the first result + // succeeds, but we have to try again to see if there's a more complex + // expression that can be produced (there isn't). + if values.iter().any(|v| *v == value1) { + return pop_stack_before_return(Ok(values.pop().unwrap())); // If not, we are done. + } + + // Otherwise, we have to try again. + values.push(value1); + } +} + +fn erase_type(s: &T) -> *const () { + s as *const T as *const () +} diff --git a/examples/formality-eg/grammar.rs b/examples/formality-eg/grammar.rs index a500d67f..83f3ee64 100644 --- a/examples/formality-eg/grammar.rs +++ b/examples/formality-eg/grammar.rs @@ -97,6 +97,9 @@ pub enum Expr { #[precedence(1)] Div(Arc, Arc), + #[grammar(($v0))] + Paren(Arc), + #[grammar(let $v0 = $v1 in $v2)] LetIn(LocalVarId, Arc, Arc), } diff --git a/examples/formality-eg/grammar/test.rs b/examples/formality-eg/grammar/test.rs index 4f60139f..39173096 100644 --- a/examples/formality-eg/grammar/test.rs +++ b/examples/formality-eg/grammar/test.rs @@ -2,7 +2,7 @@ use formality_core::test; use crate::eg::term; -use super::{StructDecl, Ty}; +use super::{Expr, StructDecl, Ty}; #[test] fn test_struct_decl() { @@ -30,6 +30,7 @@ fn test_struct_ty_no_args() { "#]] .assert_debug_eq(&r); } + #[test] fn test_vec_int_ty() { let r: Ty = term("Vec"); @@ -38,3 +39,12 @@ fn test_vec_int_ty() { "#]] .assert_debug_eq(&r); } + +#[test] +fn test_expression() { + let r: Expr = term("3 + 5 * 6"); + expect_test::expect![[r#" + 3 + 5 * 6 + "#]] + .assert_debug_eq(&r); +} From ba6ebaf121b5fbe94e694cd5588b32fc7e26fa21 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 5 Nov 2023 07:47:41 -0500 Subject: [PATCH 19/38] improve variant names --- crates/formality-macros/src/parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/formality-macros/src/parse.rs b/crates/formality-macros/src/parse.rs index 42aa2785..fea874d6 100644 --- a/crates/formality-macros/src/parse.rs +++ b/crates/formality-macros/src/parse.rs @@ -39,7 +39,7 @@ pub(crate) fn derive_parse_with_spec( let mut parse_variants = TokenStream::new(); for variant in s.variants() { - let variant_name = as_literal(variant.ast().ident); + let variant_name = Literal::string(&format!("{}::{}", s.ast().ident, variant.ast().ident)); let v = parse_variant(variant, external_spec)?; let precedence = precedence(&variant.ast().attrs)?.literal(); parse_variants.extend(quote_spanned!( From c732d22ab1e44e9d97f89b2a56607dc8d98bda65 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 5 Nov 2023 07:56:41 -0500 Subject: [PATCH 20/38] add parser torture testing framework --- tests/parser-torture-tests/ambiguity.rs | 57 ++++++++++++++++++++++++ tests/parser-torture-tests/grammar.rs | 42 +++++++++++++++++ tests/parser-torture-tests/main.rs | 28 ++++++++++++ tests/parser-torture-tests/precedence.rs | 26 +++++++++++ 4 files changed, 153 insertions(+) create mode 100644 tests/parser-torture-tests/ambiguity.rs create mode 100644 tests/parser-torture-tests/grammar.rs create mode 100644 tests/parser-torture-tests/main.rs create mode 100644 tests/parser-torture-tests/precedence.rs diff --git a/tests/parser-torture-tests/ambiguity.rs b/tests/parser-torture-tests/ambiguity.rs new file mode 100644 index 00000000..ccd1ad8e --- /dev/null +++ b/tests/parser-torture-tests/ambiguity.rs @@ -0,0 +1,57 @@ +use formality_core::term; +use std::sync::Arc; + +#[test] +#[should_panic(expected = "ambiguous parse")] // FIXME: we want this example to work +fn reduce_reduce_ok() { + #[term] + pub enum Root { + #[cast] + ClassTy(ClassTy), + #[cast] + Perm(Perm), + } + + #[term($perm $class_id)] + pub struct ClassTy { + perm: Perm, + class_id: Id, + } + + #[term] + pub enum Perm { + My, + Our, + } + + formality_core::id!(Id); + + let term: Root = crate::ptt::term("my String"); + expect_test::expect![].assert_debug_eq(&term); +} + +#[test] +#[should_panic(expected = "ambiguous parse")] +fn reduce_reduce_ambig() { + #[term] + pub enum Root { + #[grammar($v0)] + OneId(Id), + #[grammar($v0 $v1)] + TwoId(Id, Id), + #[grammar($v0 $v1)] + TwoRr(Arc, Arc), + } + + formality_core::id!(Id); + + // This will panic. It could be parsed in multiple ways + // (using a variant of Reverse Polish Notation) and none is obviously + // better than the other: + // + // Root = ((Id Root::OneId) (Id Id Root::TwoId) Root::TwoRr) + // Root = (Id Id Root::TwoId) (Id Root::OneId) Root::TwoRr) + // Root = ((Id Root::OneId) (Id Root::OneId) (Id Root::OneId) Root::TwoRr) + let term: Root = crate::ptt::term("a b c"); + expect_test::expect![].assert_debug_eq(&term); +} diff --git a/tests/parser-torture-tests/grammar.rs b/tests/parser-torture-tests/grammar.rs new file mode 100644 index 00000000..a03a8558 --- /dev/null +++ b/tests/parser-torture-tests/grammar.rs @@ -0,0 +1,42 @@ +use crate::ptt::{ + grammar::{BoundVar, ExistentialVar, UniversalVar, Variable}, + FormalityLang, +}; +use formality_core::{language::HasKind, term}; + +// Create a dummy kind/parameter -- we're not using these for the torture +// tests, but we need them. + +#[term] +#[derive(Copy)] +pub enum DummyKind { + Ty, +} + +#[term] +pub enum DummyParameter { + #[cast] + Ty(DummyTy), +} + +#[term] +pub enum DummyTy { + #[variable] + Variable(Variable), +} + +formality_core::cast_impl!((BoundVar) <: (Variable) <: (DummyTy)); +formality_core::cast_impl!((ExistentialVar) <: (Variable) <: (DummyTy)); +formality_core::cast_impl!((UniversalVar) <: (Variable) <: (DummyTy)); +formality_core::cast_impl!((Variable) <: (DummyTy) <: (DummyParameter)); +formality_core::cast_impl!((BoundVar) <: (DummyTy) <: (DummyParameter)); +formality_core::cast_impl!((ExistentialVar) <: (DummyTy) <: (DummyParameter)); +formality_core::cast_impl!((UniversalVar) <: (DummyTy) <: (DummyParameter)); + +impl HasKind for DummyParameter { + fn kind(&self) -> formality_core::language::CoreKind { + match self { + DummyParameter::Ty(_) => DummyKind::Ty, + } + } +} diff --git a/tests/parser-torture-tests/main.rs b/tests/parser-torture-tests/main.rs new file mode 100644 index 00000000..8a922c31 --- /dev/null +++ b/tests/parser-torture-tests/main.rs @@ -0,0 +1,28 @@ +mod ambiguity; +mod grammar; +mod precedence; + +formality_core::declare_language! { + mod ptt { + const NAME = "PTT"; + type Kind = crate::grammar::DummyKind; + type Parameter = crate::grammar::DummyParameter; + const BINDING_OPEN = '<'; + const BINDING_CLOSE = '>'; + const KEYWORDS = [ + "struct", + "fn", + "let", + "in", + "integer", + ]; + } +} + +// Default language for our crate +use formality_core::Fallible; +use ptt::FormalityLang; + +fn main() -> Fallible<()> { + Ok(()) +} diff --git a/tests/parser-torture-tests/precedence.rs b/tests/parser-torture-tests/precedence.rs new file mode 100644 index 00000000..ff4fa143 --- /dev/null +++ b/tests/parser-torture-tests/precedence.rs @@ -0,0 +1,26 @@ +use formality_core::term; +use std::sync::Arc; + +#[test] +fn precedence() { + #[term] + pub enum Root { + #[cast] + Id(Id), + + #[grammar($v0 + $v1)] + Add(Arc, Arc), + + #[grammar($v0 * $v1)] + #[precedence(1)] + Mul(Arc, Arc), + } + + formality_core::id!(Id); + + let term: Root = crate::ptt::term("a + b * c"); + expect_test::expect![[r#" + a + b * c + "#]] + .assert_debug_eq(&term); +} From cf3141c63e0fa6a831e0d576541da4ace65c84a7 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 5 Nov 2023 08:18:05 -0500 Subject: [PATCH 21/38] introduce cast variant reduction suppression This helps resolve some annoying ambiguities while keeping the ones I think we want. --- crates/formality-core/src/parse/parser.rs | 44 ++++++++++++++++++++++- crates/formality-macros/src/parse.rs | 1 + tests/parser-torture-tests/ambiguity.rs | 13 ++++--- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/crates/formality-core/src/parse/parser.rs b/crates/formality-core/src/parse/parser.rs index 1cd96275..e68596c7 100644 --- a/crates/formality-core/src/parse/parser.rs +++ b/crates/formality-core/src/parse/parser.rs @@ -57,6 +57,7 @@ where scope: &'s Scope, text: &'t str, reductions: Vec<&'static str>, + is_cast_variant: bool, } impl<'s, 't, T, L> Parser<'s, 't, T, L> @@ -117,12 +118,14 @@ where /// Shorthand for `parse_variant` where the parsing operation is to /// parse the type `V` and then upcast it to the desired result type. + /// Also marks the variant as a cast variant. pub fn parse_variant_cast(&mut self, variant_precedence: usize) where V: CoreParse + Upcast, { let variant_name = std::any::type_name::(); Self::parse_variant(self, variant_name, variant_precedence, |p| { + p.mark_as_cast_variant(); let v: V = p.nonterminal()?; Ok(v.upcast()) }) @@ -151,6 +154,7 @@ where scope: self.scope, text: self.start_text, reductions: vec![], + is_cast_variant: false, }; let result = op(&mut active_variant); @@ -160,7 +164,12 @@ where match result { Ok(value) => { - active_variant.reductions.push(variant_name); + // Subtle: for cast variants, don't record the variant name in the reduction lits, + // as it doesn't carry semantic weight. See `mark_as_cast_variant` for more details. + if !active_variant.is_cast_variant { + active_variant.reductions.push(variant_name); + } + self.successes.push(( SuccessfulParse { text: active_variant.text, @@ -280,6 +289,37 @@ where self.text = skip_trailing_comma(self.text); } + /// Marks this variant as an cast variant, + /// which means there is no semantic difference + /// between the thing you parsed and the reduced form. + /// We do this automatically for enum variants marked + /// as `#[cast]` or calls to `parse_variant_cast`. + /// + /// Cast variants interact differently with ambiguity detection. + /// Consider this grammar: + /// + /// ``` + /// X = Y | Z // X has two variants + /// Y = A // Y has 1 variant + /// Z = A B // Z has 1 variant + /// A = "a" // A has 1 variant + /// B = "b" // B has 1 variant + /// ``` + /// + /// If you mark the two `X` variants (`X = Y` and `X = Z`) + /// as cast variants, then the input `"a b"` is considered + /// unambiguous and is parsed as `X = (Z = (A = "a') (B = "b))` + /// with no remainder. + /// + /// If you don't mark those variants as cast variants, + /// then we consider this *ambiguous*, because + /// it could be that you want `X = (Y = (A = "a"))` with + /// a remainder of `"b"`. This is appropriate + /// if choosing Y vs Z has different semantic meaning. + pub fn mark_as_cast_variant(&mut self) { + self.is_cast_variant = true; + } + /// Expect *exactly* the given text (after skipping whitespace) /// in the input string. Reports an error if anything else is observed. /// In error case, consumes only whitespace. @@ -349,6 +389,7 @@ where text: self.text, reductions: vec![], scope: self.scope, + is_cast_variant: false, }; match this.identifier_like_string() { @@ -399,6 +440,7 @@ where scope: &scope, text: self.text, reductions: vec![], + is_cast_variant: false, }; let result = op(&mut av); self.text = av.text; diff --git a/crates/formality-macros/src/parse.rs b/crates/formality-macros/src/parse.rs index fea874d6..ecba9866 100644 --- a/crates/formality-macros/src/parse.rs +++ b/crates/formality-macros/src/parse.rs @@ -104,6 +104,7 @@ fn parse_variant( let construct = variant.construct(field_ident); Ok(quote_spanned! { ast.ident.span() => + __p.mark_as_cast_variant(); #(#build)* Ok(#construct) }) diff --git a/tests/parser-torture-tests/ambiguity.rs b/tests/parser-torture-tests/ambiguity.rs index ccd1ad8e..6427712c 100644 --- a/tests/parser-torture-tests/ambiguity.rs +++ b/tests/parser-torture-tests/ambiguity.rs @@ -1,8 +1,7 @@ -use formality_core::term; +use formality_core::{term, test}; use std::sync::Arc; #[test] -#[should_panic(expected = "ambiguous parse")] // FIXME: we want this example to work fn reduce_reduce_ok() { #[term] pub enum Root { @@ -27,7 +26,10 @@ fn reduce_reduce_ok() { formality_core::id!(Id); let term: Root = crate::ptt::term("my String"); - expect_test::expect![].assert_debug_eq(&term); + expect_test::expect![[r#" + my String + "#]] + .assert_debug_eq(&term); } #[test] @@ -53,5 +55,8 @@ fn reduce_reduce_ambig() { // Root = (Id Id Root::TwoId) (Id Root::OneId) Root::TwoRr) // Root = ((Id Root::OneId) (Id Root::OneId) (Id Root::OneId) Root::TwoRr) let term: Root = crate::ptt::term("a b c"); - expect_test::expect![].assert_debug_eq(&term); + expect_test::expect![[r#" + a b c + "#]] + .assert_debug_eq(&term); } From 3cf2831bcad8df04d5560ebb02f214bff7e11081 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 5 Nov 2023 08:37:21 -0500 Subject: [PATCH 22/38] fix subtle bug in `is_preferable` and add a test --- crates/formality-core/src/parse/parser.rs | 3 +- tests/parser-torture-tests/precedence.rs | 68 +++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/crates/formality-core/src/parse/parser.rs b/crates/formality-core/src/parse/parser.rs index e68596c7..1f1d5acc 100644 --- a/crates/formality-core/src/parse/parser.rs +++ b/crates/formality-core/src/parse/parser.rs @@ -266,7 +266,8 @@ where l1.len() > l2.len() && (0..l2.len()).all(|i| l1[i] == l2[i]) } - prec_i > prec_j || has_prefix(&parse_i.reductions, &parse_j.reductions) + prec_i > prec_j + || (prec_i == prec_j && has_prefix(&parse_i.reductions, &parse_j.reductions)) } } diff --git a/tests/parser-torture-tests/precedence.rs b/tests/parser-torture-tests/precedence.rs index ff4fa143..51b3a7db 100644 --- a/tests/parser-torture-tests/precedence.rs +++ b/tests/parser-torture-tests/precedence.rs @@ -24,3 +24,71 @@ fn precedence() { "#]] .assert_debug_eq(&term); } + +#[test] +#[should_panic(expected = "extra tokens")] +fn higher_precedence_less_reductions_1() { + // Subtle: In this case, A has higher precedence, + // so even though we COULD parse the entire string, + // we prefer to just parse the first identifier, + // which results in a panic. + // + // In the past, we had a bug in our preference function + // that caused it to be order dependent, sometimes + // panicking and sometimes not. Hence we have a similar + // test with opposite order. + + #[term] + pub enum Root { + #[cast] + #[precedence(1)] + A(A), + + #[cast] + B(B), + } + + #[term($v0)] + pub struct A(Id); + + #[term($v0 $v1)] + pub struct B(A, Id); + + formality_core::id!(Id); + + let term: Root = crate::ptt::term("my String"); + expect_test::expect![[r#" + "#]] + .assert_debug_eq(&term); +} + +#[test] +#[should_panic(expected = "extra tokens")] +fn higher_precedence_less_reductions_2() { + // Same as `higher_precedence_less_reductions_1` but with + // opposite term order. See explanation in that function. + + #[term] + pub enum Root { + #[cast] + B(B), + + #[cast] + #[precedence(1)] + A(A), + } + + #[term($v0)] + pub struct A(Id); + + #[term($v0 $v1)] + pub struct B(A, Id); + + formality_core::id!(Id); + + let term: Root = crate::ptt::term("my String"); + expect_test::expect![[r#" + my String + "#]] + .assert_debug_eq(&term); +} From 96f70776bcfc259dbb69691ec39cf7c34c582237 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 5 Nov 2023 08:46:04 -0500 Subject: [PATCH 23/38] document and add some more tests --- book/src/formality_core/parse.md | 25 +++++++++++++++- tests/parser-torture-tests/precedence.rs | 37 +++++++++++++++--------- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/book/src/formality_core/parse.md b/book/src/formality_core/parse.md index b82f7a5f..6798ec04 100644 --- a/book/src/formality_core/parse.md +++ b/book/src/formality_core/parse.md @@ -25,9 +25,23 @@ When parsing an enum there will be multiple possibilities. We will attempt to pa * Explicit precedence: By default, every variant has precedence 0, but you can override this by annotating variants with `#[precedence(N)]` (where `N` is some integer). This will override the precedence for that variant. Variants with higher precedences are preferred. * Reduction prefix: When parsing, we track the list of things we had to parse. If there are two variants at the same precedence level, but one of them had to parse strictly more things than the other and in the same way, we'll prefer the longer one. So for example if one variant parsed a `Ty` and the other parsed a `Ty Ty`, we'd take the `Ty Ty`. + * When considering whether a reduction is "significant", we take casts into account. See `ActiveVariant::mark_as_cast_variant` for a more detailed explanation and set of examples. Otherwise, the parser will panic and report ambiguity. The parser panics rather than returning an error because ambiguity doesn't mean that there is no way to parse the given text as the nonterminal -- rather that there are multiple ways. Errors mean that the text does not match the grammar for that nonterminal. +### Left-recursive grammars + +We permit left recursive grammars like: + +``` +Expr = Expr + Expr + | integer +``` + +We *always* bias towards greedy parses, so `a + b + c` parses as `(a + b) + c`. +This might occasionally not be what you want. +Sorry. + ### Symbols A grammar consists of a series of *symbols*. Each symbol matches some text in the input string. Symbols come in two varieties: @@ -39,11 +53,20 @@ A grammar consists of a series of *symbols*. Each symbol matches some text in th * If fields have names, then `$field` should name the field. * For position fields (e.g., the T and U in `Mul(Expr, Expr)`), use `$v0`, `$v1`, etc. * Exception: `$$` is treated as the terminal `'$'`. -* Nonterminals can also accept modes: +* Nonterminals have various modes: * `$field` -- just parse the field's type * `$*field` -- the field must be a `Vec` -- parse any number of `T` instances. Something like `[ $*field ]` would parse `[f1 f2 f3]`, assuming `f1`, `f2`, and `f3` are valid values for `field`. * `$,field` -- similar to the above, but uses a comma separated list (with optional trailing comma). So `[ $,field ]` will parse something like `[f1, f2, f3]`. * `$?field` -- will parse `field` and use `Default::default()` value if not present. + * `$` -- parse ``, where `field: Vec` + * `$` -- parse ``, where `field: Vec`, but accept empty string as empty vector + * `$(field)` -- parse `(E1, E2, E3)`, where `field: Vec` + * `$(?field)` -- parse `(E1, E2, E3)`, where `field: Vec`, but accept empty string as empty vector + * `$[field]` -- parse `[E1, E2, E3]`, where `field: Vec` + * `$[?field]` -- parse `[E1, E2, E3]`, where `field: Vec`, but accept empty string as empty vector + * `${field}` -- parse `{E1, E2, E3}`, where `field: Vec` + * `${?field}` -- parse `{E1, E2, E3}`, where `field: Vec`, but accept empty string as empty vector + * `$:guard ` -- parses `` but only if the keyword `guard` is present. For example, `$:where $,where_clauses` would parse `where WhereClause1, WhereClause2, WhereClause3` ### Greediness diff --git a/tests/parser-torture-tests/precedence.rs b/tests/parser-torture-tests/precedence.rs index 51b3a7db..a934cdf8 100644 --- a/tests/parser-torture-tests/precedence.rs +++ b/tests/parser-torture-tests/precedence.rs @@ -1,24 +1,33 @@ use formality_core::term; use std::sync::Arc; -#[test] -fn precedence() { - #[term] - pub enum Root { - #[cast] - Id(Id), +#[term] +pub enum Expr { + #[cast] + Id(Id), - #[grammar($v0 + $v1)] - Add(Arc, Arc), + #[grammar($v0 + $v1)] + Add(Arc, Arc), - #[grammar($v0 * $v1)] - #[precedence(1)] - Mul(Arc, Arc), - } + #[grammar($v0 * $v1)] + #[precedence(1)] + Mul(Arc, Arc), +} - formality_core::id!(Id); +formality_core::id!(Id); - let term: Root = crate::ptt::term("a + b * c"); +#[test] +fn mul_is_higher_precedence() { + let term: Expr = crate::ptt::term("a + b * c"); + expect_test::expect![[r#" + a + b * c + "#]] + .assert_debug_eq(&term); +} + +#[test] +fn equal_precedence_panics() { + let term: Expr = crate::ptt::term("a + b * c"); expect_test::expect![[r#" a + b * c "#]] From d7946b60d6eb039494e1d43878e14fb29fa4c3e5 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 6 Nov 2023 08:43:46 -0500 Subject: [PATCH 24/38] modify `{:#?}` to print the traditional debug It is sometimes useful to see EXACTLY what is going on --- crates/formality-macros/src/debug.rs | 43 ++++++++++++++++-- .../src/prove/minimize/test.rs | 37 ++-------------- examples/formality-eg/grammar/test.rs | 44 ++++++++++++++++--- tests/parser-torture-tests/ambiguity.rs | 7 ++- tests/parser-torture-tests/precedence.rs | 29 +++++++++++- 5 files changed, 116 insertions(+), 44 deletions(-) diff --git a/crates/formality-macros/src/debug.rs b/crates/formality-macros/src/debug.rs index 779b6829..98401013 100644 --- a/crates/formality-macros/src/debug.rs +++ b/crates/formality-macros/src/debug.rs @@ -1,4 +1,5 @@ extern crate proc_macro; + use convert_case::{Case, Casing}; use proc_macro2::{Ident, Literal, TokenStream}; use quote::{quote, quote_spanned}; @@ -20,6 +21,7 @@ pub(crate) fn derive_debug_with_spec( .into_compile_error(); } + let default_debug = default_debug_variant(&s); let debug_arms = s.each_variant(|v| debug_variant(v, external_spec)); s.gen_impl(quote! { @@ -29,15 +31,50 @@ pub(crate) fn derive_debug_with_spec( fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { #[allow(unused_assignments)] - match self { - #debug_arms + if fmt.alternate() { + #default_debug + } else { + match self { + #debug_arms + } + Ok(()) } - Ok(()) } } }) } +fn default_debug_variant(s: &synstructure::Structure) -> TokenStream { + let arms = s.each_variant(|v| { + let fields: TokenStream = v.bindings().iter().map(|bi| { + if let Some(name) = &bi.ast().ident { + let name = as_literal(name); + quote_spanned!(name.span() => .field(#name, #bi)) + } else { + quote_spanned!(bi.span() => .field(#bi)) + } + }).collect(); + let variant_name = as_literal(v.ast().ident); + match v.ast().fields { + syn::Fields::Named(_) => { + quote_spanned!(variant_name.span() => fmt.debug_struct(#variant_name) #fields .finish()) + } + syn::Fields::Unnamed(_) => { + quote_spanned!(variant_name.span() => fmt.debug_tuple(#variant_name) #fields .finish()) + } + syn::Fields::Unit => { + quote_spanned!(variant_name.span() => fmt.debug_tuple(#variant_name) .finish()) + } + } + }); + + quote_spanned! { s.ast().span() => + match self { + #arms + } + } +} + fn debug_variant( variant: &synstructure::VariantInfo, external_spec: Option<&FormalitySpec>, diff --git a/crates/formality-prove/src/prove/minimize/test.rs b/crates/formality-prove/src/prove/minimize/test.rs index 6242d8a2..a80688a2 100644 --- a/crates/formality-prove/src/prove/minimize/test.rs +++ b/crates/formality-prove/src/prove/minimize/test.rs @@ -17,42 +17,13 @@ fn minimize_a() { let (env, subst) = env.existential_substitution(&term); let term = term.instantiate_with(&subst).unwrap(); - expect![[r#" - ( - Env { - variables: [ - ?ty_1, - ?ty_2, - ?ty_3, - ], - coherence_mode: false, - }, - [ - ?ty_1, - ?ty_3, - ], - ) - "#]] - .assert_debug_eq(&(&env, &term)); + expect!["(Env { variables: [?ty_1, ?ty_2, ?ty_3], coherence_mode: false }, [?ty_1, ?ty_3])"] + .assert_eq(&format!("{:?}", (&env, &term))); let (mut env_min, term_min, m) = minimize(env, term); - expect![[r#" - ( - Env { - variables: [ - ?ty_0, - ?ty_1, - ], - coherence_mode: false, - }, - [ - ?ty_0, - ?ty_1, - ], - ) - "#]] - .assert_debug_eq(&(&env_min, &term_min)); + expect!["(Env { variables: [?ty_0, ?ty_1], coherence_mode: false }, [?ty_0, ?ty_1])"] + .assert_eq(&format!("{:?}", (&env_min, &term_min))); let ty0 = term_min[0].as_variable().unwrap(); let ty1 = term_min[1].as_variable().unwrap(); diff --git a/examples/formality-eg/grammar/test.rs b/examples/formality-eg/grammar/test.rs index 39173096..919fa4f5 100644 --- a/examples/formality-eg/grammar/test.rs +++ b/examples/formality-eg/grammar/test.rs @@ -8,7 +8,10 @@ use super::{Expr, StructDecl, Ty}; fn test_struct_decl() { let r: StructDecl = term("struct Point { x: integer, y: integer }"); expect_test::expect![[r#" - struct Point { x : integer, y : integer } + StructDecl { + id: Point, + bound: { x : integer, y : integer }, + } "#]] .assert_debug_eq(&r); } @@ -17,7 +20,12 @@ fn test_struct_decl() { fn test_struct_ty_empty_args() { let r: Ty = term("Point"); expect_test::expect![[r#" - Point + StructTy( + StructTy { + id: Point, + parameters: [], + }, + ) "#]] .assert_debug_eq(&r); } @@ -26,7 +34,12 @@ fn test_struct_ty_empty_args() { fn test_struct_ty_no_args() { let r: Ty = term("Point"); expect_test::expect![[r#" - Point + StructTy( + StructTy { + id: Point, + parameters: [], + }, + ) "#]] .assert_debug_eq(&r); } @@ -35,7 +48,16 @@ fn test_struct_ty_no_args() { fn test_vec_int_ty() { let r: Ty = term("Vec"); expect_test::expect![[r#" - Vec + StructTy( + StructTy { + id: Vec, + parameters: [ + Ty( + Integer, + ), + ], + }, + ) "#]] .assert_debug_eq(&r); } @@ -44,7 +66,19 @@ fn test_vec_int_ty() { fn test_expression() { let r: Expr = term("3 + 5 * 6"); expect_test::expect![[r#" - 3 + 5 * 6 + Add( + IntegerLiteral( + 3, + ), + Mul( + IntegerLiteral( + 5, + ), + IntegerLiteral( + 6, + ), + ), + ) "#]] .assert_debug_eq(&r); } diff --git a/tests/parser-torture-tests/ambiguity.rs b/tests/parser-torture-tests/ambiguity.rs index 6427712c..3275fb61 100644 --- a/tests/parser-torture-tests/ambiguity.rs +++ b/tests/parser-torture-tests/ambiguity.rs @@ -27,7 +27,12 @@ fn reduce_reduce_ok() { let term: Root = crate::ptt::term("my String"); expect_test::expect![[r#" - my String + ClassTy( + ClassTy { + perm: My, + class_id: String, + }, + ) "#]] .assert_debug_eq(&term); } diff --git a/tests/parser-torture-tests/precedence.rs b/tests/parser-torture-tests/precedence.rs index a934cdf8..07089270 100644 --- a/tests/parser-torture-tests/precedence.rs +++ b/tests/parser-torture-tests/precedence.rs @@ -20,16 +20,41 @@ formality_core::id!(Id); fn mul_is_higher_precedence() { let term: Expr = crate::ptt::term("a + b * c"); expect_test::expect![[r#" - a + b * c + Add( + Id( + a, + ), + Mul( + Id( + b, + ), + Id( + c, + ), + ), + ) "#]] .assert_debug_eq(&term); } #[test] +// FIXME #[should_panic(expected = "ambiguous parse")] fn equal_precedence_panics() { let term: Expr = crate::ptt::term("a + b * c"); expect_test::expect![[r#" - a + b * c + Add( + Id( + a, + ), + Mul( + Id( + b, + ), + Id( + c, + ), + ), + ) "#]] .assert_debug_eq(&term); } From a88c9cfb8a0af24addc8cac3c7be0b4134103cbf Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 6 Nov 2023 08:44:26 -0500 Subject: [PATCH 25/38] fix test to parse `a + b + c` behavior is still broken --- tests/parser-torture-tests/precedence.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/parser-torture-tests/precedence.rs b/tests/parser-torture-tests/precedence.rs index 07089270..43ca5f02 100644 --- a/tests/parser-torture-tests/precedence.rs +++ b/tests/parser-torture-tests/precedence.rs @@ -40,13 +40,13 @@ fn mul_is_higher_precedence() { #[test] // FIXME #[should_panic(expected = "ambiguous parse")] fn equal_precedence_panics() { - let term: Expr = crate::ptt::term("a + b * c"); + let term: Expr = crate::ptt::term("a + b + c"); expect_test::expect![[r#" Add( Id( a, ), - Mul( + Add( Id( b, ), From 0f0460979932d50d6a2e9f4bd726d83e18cdf562 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 7 Nov 2023 05:27:27 -0500 Subject: [PATCH 26/38] fix doctest --- crates/formality-core/src/parse/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/formality-core/src/parse/parser.rs b/crates/formality-core/src/parse/parser.rs index 1f1d5acc..5de65bf1 100644 --- a/crates/formality-core/src/parse/parser.rs +++ b/crates/formality-core/src/parse/parser.rs @@ -299,7 +299,7 @@ where /// Cast variants interact differently with ambiguity detection. /// Consider this grammar: /// - /// ``` + /// ```text /// X = Y | Z // X has two variants /// Y = A // Y has 1 variant /// Z = A B // Z has 1 variant From 901d2e1fa196a4cd0af379b9c8a6b2250ce85139 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 7 Nov 2023 05:27:41 -0500 Subject: [PATCH 27/38] refactor out a general `reject` method We are going to rework the way that variables are given precedence to use explicit rejection instead. --- crates/formality-core/src/parse/parser.rs | 56 ++++++++++++++++------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/crates/formality-core/src/parse/parser.rs b/crates/formality-core/src/parse/parser.rs index 5de65bf1..e725f1ff 100644 --- a/crates/formality-core/src/parse/parser.rs +++ b/crates/formality-core/src/parse/parser.rs @@ -380,12 +380,27 @@ where } } - /// Reject next identifier-like string if it is one of the given list of keywords. - /// Does not consume any input. - /// You can this to implement positional keywords -- just before parsing an identifier or variable, - /// you can invoke `reject_custom_keywords` to reject anything that you don't want to permit in this position. + /// Accepts any of the given keywords. #[tracing::instrument(level = "trace", ret)] - pub fn reject_custom_keywords(&self, keywords: &[&str]) -> Result<(), Set>> { + pub fn expect_keyword_in(&mut self, expected: &[&str]) -> Result>> { + let text0 = self.text; + match self.identifier_like_string() { + Ok(ident) if expected.iter().any(|&kw| ident == kw) => Ok(ident), + _ => Err(ParseError::at( + skip_whitespace(text0), + format!("expected any of `{:?}`", expected), + )), + } + } + + /// Attempts to execute `op` and, if it successfully parses, then returns an error + /// with the value (which is meant to be incorporated into a par) + /// If `op` fails to parse then the result is `Ok`. + pub fn reject( + &self, + op: impl Fn(&mut ActiveVariant<'s, 't, L>) -> Result>>, + err: impl FnOnce(T) -> Set>, + ) -> Result<(), Set>> { let mut this = ActiveVariant { text: self.text, reductions: vec![], @@ -393,22 +408,29 @@ where is_cast_variant: false, }; - match this.identifier_like_string() { - Ok(ident) => { - if keywords.iter().any(|&kw| kw == ident) { - return Err(ParseError::at( - self.text, - format!("expected identified, found keyword `{ident:?}`"), - )); - } - - Ok(()) - } - + match op(&mut this) { + Ok(value) => Err(err(value)), Err(_) => Ok(()), } } + /// Reject next identifier-like string if it is one of the given list of keywords. + /// Does not consume any input. + /// You can this to implement positional keywords -- just before parsing an identifier or variable, + /// you can invoke `reject_custom_keywords` to reject anything that you don't want to permit in this position. + #[tracing::instrument(level = "trace", ret)] + pub fn reject_custom_keywords(&self, keywords: &[&str]) -> Result<(), Set>> { + self.reject( + |p| p.expect_keyword_in(keywords), + |ident| { + ParseError::at( + self.text, + format!("expected identified, found keyword `{ident:?}`"), + ) + }, + ) + } + /// Extracts a string that meets the regex for an identifier /// (but it could also be a keyword). #[tracing::instrument(level = "trace", ret)] From 606a240e508f32bf2e4703f9c339ba4082296b40 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 7 Nov 2023 05:40:28 -0500 Subject: [PATCH 28/38] automatically reject variable names When one variant is tagged as `#[variable]`, we now automatically reject variable names from other variants. This means we can use `#[precedence]` just to encode true *precedence* (i.e., of operators). This was important since there was a conflict where variables wanted to be the same *precedence* as other identifiers in a logical sense. --- crates/formality-core/src/language.rs | 4 +- crates/formality-core/src/parse/parser.rs | 19 +++++++- crates/formality-macros/src/parse.rs | 46 ++++++++++++++------ crates/formality-types/src/grammar/consts.rs | 1 - crates/formality-types/src/grammar/ty.rs | 1 - crates/formality-types/src/lib.rs | 5 ++- 6 files changed, 56 insertions(+), 20 deletions(-) diff --git a/crates/formality-core/src/language.rs b/crates/formality-core/src/language.rs index 02aeaaaf..91abf1a1 100644 --- a/crates/formality-core/src/language.rs +++ b/crates/formality-core/src/language.rs @@ -1,6 +1,7 @@ use crate::cast::UpcastFrom; use crate::term::CoreTerm; use crate::variable::{CoreBoundVar, CoreExistentialVar, CoreUniversalVar, CoreVariable}; +use crate::DowncastTo; use std::fmt::Debug; use std::hash::Hash; @@ -20,7 +21,8 @@ pub trait Language: 'static + Copy + Ord + Hash + Debug + Default { + UpcastFrom> + UpcastFrom> + UpcastFrom> - + UpcastFrom>; + + UpcastFrom> + + DowncastTo>; /// The token (typically `<`) used to open binders. const BINDING_OPEN: char; diff --git a/crates/formality-core/src/parse/parser.rs b/crates/formality-core/src/parse/parser.rs index e725f1ff..4dac5892 100644 --- a/crates/formality-core/src/parse/parser.rs +++ b/crates/formality-core/src/parse/parser.rs @@ -3,7 +3,9 @@ use std::str::FromStr; use crate::{ language::{CoreParameter, HasKind, Language}, - set, Downcast, DowncastFrom, Set, Upcast, + set, + variable::CoreVariable, + Downcast, DowncastFrom, Set, Upcast, }; use super::{CoreParse, ParseError, ParseResult, Scope, SuccessfulParse, TokenResult}; @@ -471,6 +473,21 @@ where result } + /// Returns an error if an in-scope variable name is found. + /// The derive automatically inserts calls to this for all other variants + /// if any variant is declared `#[variable]`. + pub fn reject_variable(&self) -> Result<(), Set>> { + self.reject::>( + |p| p.variable(), + |var| { + ParseError::at( + self.text, + format!("found unexpected in-scope variable {:?}", var), + ) + }, + ) + } + /// Parses the next identifier as a variable in scope /// with the kind appropriate for the return type `R`. /// diff --git a/crates/formality-macros/src/parse.rs b/crates/formality-macros/src/parse.rs index ecba9866..f920d63a 100644 --- a/crates/formality-macros/src/parse.rs +++ b/crates/formality-macros/src/parse.rs @@ -37,10 +37,17 @@ pub(crate) fn derive_parse_with_spec( } } + // Determine whether *any* variant is marked as `#[variable]`. + // If so, all *other* variants will automatically reject in-scope variable names. + let any_variable_variant = s + .variants() + .iter() + .any(|v| has_variable_attr(v.ast().attrs)); + let mut parse_variants = TokenStream::new(); for variant in s.variants() { let variant_name = Literal::string(&format!("{}::{}", s.ast().ident, variant.ast().ident)); - let v = parse_variant(variant, external_spec)?; + let v = parse_variant(variant, external_spec, any_variable_variant)?; let precedence = precedence(&variant.ast().attrs)?.literal(); parse_variants.extend(quote_spanned!( variant.ast().ident.span() => @@ -66,17 +73,27 @@ pub(crate) fn derive_parse_with_spec( fn parse_variant( variant: &synstructure::VariantInfo, external_spec: Option<&FormalitySpec>, + any_variable_variant: bool, ) -> syn::Result { let ast = variant.ast(); + let has_variable_attr = has_variable_attr(ast.attrs); + let mut stream = TokenStream::default(); + + // If there are variable variants -- but this is not one -- then we always begin by rejecting + // variable names. This avoids ambiguity in a case like `for { X : Debug }`, where we + // want to parse `X` as a type variable. + if any_variable_variant && !has_variable_attr { + stream.extend(quote_spanned!(ast.ident.span() => __p.reject_variable()?;)); + } // When invoked like `#[term(foo)]`, use the spec from `foo` if let Some(spec) = external_spec { - return parse_variant_with_attr(variant, spec); + return parse_variant_with_attr(variant, spec, stream); } // Else, look for a `#[grammar]` attribute on the variant if let Some(attr) = get_grammar_attr(ast.attrs) { - return parse_variant_with_attr(variant, &attr?); + return parse_variant_with_attr(variant, &attr?, stream); } // If no `#[grammar(...)]` attribute is provided, then we provide default behavior. @@ -85,35 +102,35 @@ fn parse_variant( // No bindings (e.g., `Foo`) -- just parse a keyword `foo` let literal = Literal::string(&to_parse_ident(ast.ident)); let construct = variant.construct(|_, _| quote! {}); - Ok(quote_spanned! { + stream.extend(quote_spanned! { ast.ident.span() => __p.expect_keyword(#literal)?; Ok(#construct) - }) - } else if has_variable_attr(variant.ast().attrs) { + }); + } else if has_variable_attr { // Has the `#[variable]` attribute -- parse an identifier and then check to see if it is present // in the scope. If so, downcast it and check that it has the correct kind. - Ok(quote_spanned! { + stream.extend(quote_spanned! { ast.ident.span() => let v = __p.variable()?; Ok(v) - }) + }); } else if has_cast_attr(variant.ast().attrs) { // Has the `#[cast]` attribute -- just parse the bindings (comma separated, if needed) let build: Vec = parse_bindings(variant.bindings()); let construct = variant.construct(field_ident); - Ok(quote_spanned! { + stream.extend(quote_spanned! { ast.ident.span() => __p.mark_as_cast_variant(); #(#build)* Ok(#construct) - }) + }); } else { // Otherwise -- parse `variant(binding0, ..., bindingN)` let literal = Literal::string(&to_parse_ident(ast.ident)); let build: Vec = parse_bindings(variant.bindings()); let construct = variant.construct(field_ident); - Ok(quote_spanned! { + stream.extend(quote_spanned! { ast.ident.span() => __p.expect_keyword(#literal)?; __p.expect_char('(')?; @@ -121,8 +138,10 @@ fn parse_variant( __p.skip_trailing_comma(); __p.expect_char(')')?; Ok(#construct) - }) + }); } + + Ok(stream) } /// When a type is given a formality attribute, we use that to guide parsing: @@ -144,9 +163,8 @@ fn parse_variant( fn parse_variant_with_attr( variant: &synstructure::VariantInfo, spec: &FormalitySpec, + mut stream: TokenStream, ) -> syn::Result { - let mut stream = TokenStream::new(); - for symbol in &spec.symbols { stream.extend(match symbol { spec::FormalitySpecSymbol::Field { name, mode } => { diff --git a/crates/formality-types/src/grammar/consts.rs b/crates/formality-types/src/grammar/consts.rs index 496ea76f..ca9bb73f 100644 --- a/crates/formality-types/src/grammar/consts.rs +++ b/crates/formality-types/src/grammar/consts.rs @@ -46,7 +46,6 @@ pub enum ConstData { Value(ValTree, Ty), #[variable] - #[precedence(1)] Variable(Variable), } diff --git a/crates/formality-types/src/grammar/ty.rs b/crates/formality-types/src/grammar/ty.rs index 66b1904d..e04cfaaa 100644 --- a/crates/formality-types/src/grammar/ty.rs +++ b/crates/formality-types/src/grammar/ty.rs @@ -107,7 +107,6 @@ pub enum TyData { #[cast] PredicateTy(PredicateTy), #[variable] - #[precedence(1)] Variable(Variable), } diff --git a/crates/formality-types/src/lib.rs b/crates/formality-types/src/lib.rs index 6773d975..89adb25c 100644 --- a/crates/formality-types/src/lib.rs +++ b/crates/formality-types/src/lib.rs @@ -16,8 +16,9 @@ formality_core::declare_language! { "enum", "union", "const", - "ty", - "lt", + "true", + "false", + "static", ]; } } From e95500ca61c89a26fd49be6fa736fe5ee54b6ea7 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 7 Nov 2023 05:48:14 -0500 Subject: [PATCH 29/38] move precedence into `SuccessfulParse` We are going to need access to it when we recurse. Besides, the code looks cleaner this way! --- crates/formality-core/src/parse.rs | 18 +++++------ crates/formality-core/src/parse/parser.rs | 37 ++++++++++------------- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/crates/formality-core/src/parse.rs b/crates/formality-core/src/parse.rs index 363729c4..a410cb5c 100644 --- a/crates/formality-core/src/parse.rs +++ b/crates/formality-core/src/parse.rs @@ -24,7 +24,7 @@ pub trait CoreParse: Sized + Debug + Clone + Eq + 'static { } mod parser; -pub use parser::{skip_whitespace, ActiveVariant, Parser}; +pub use parser::{skip_whitespace, ActiveVariant, Parser, Precedence}; /// Parses `text` as a term with the given bindings in scope. /// @@ -76,21 +76,15 @@ pub struct SuccessfulParse<'t, T> { /// reduction. reductions: Vec<&'static str>, + /// The precedence of this parse, which is derived from the value given + /// to `parse_variant`. + precedence: Precedence, + /// The value produced. value: T, } impl<'t, T> SuccessfulParse<'t, T> { - #[track_caller] - pub fn new(text: &'t str, reductions: Vec<&'static str>, value: T) -> Self { - // assert!(!reductions.is_empty()); - Self { - text, - reductions, - value, - } - } - /// Extract the value parsed and the remaining text, /// ignoring the reductions. pub fn finish(self) -> (T, &'t str) { @@ -103,6 +97,7 @@ impl<'t, T> SuccessfulParse<'t, T> { SuccessfulParse { text: self.text, reductions: self.reductions, + precedence: self.precedence, value: op(self.value), } } @@ -117,6 +112,7 @@ where SuccessfulParse { text: term.text, reductions: term.reductions, + precedence: term.precedence, value: term.value.upcast(), } } diff --git a/crates/formality-core/src/parse/parser.rs b/crates/formality-core/src/parse/parser.rs index 4dac5892..829657cd 100644 --- a/crates/formality-core/src/parse/parser.rs +++ b/crates/formality-core/src/parse/parser.rs @@ -30,12 +30,12 @@ where scope: &'s Scope, start_text: &'t str, nonterminal_name: &'static str, - successes: Vec<(SuccessfulParse<'t, T>, Precedence)>, + successes: Vec>, failures: Set>, } -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -struct Precedence(usize); +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub struct Precedence(usize); /// The "active variant" struct is the main struct /// you will use if you are writing custom parsing logic. @@ -152,6 +152,8 @@ where ); let guard = span.enter(); + let variant_precedence = Precedence(variant_precedence); + let mut active_variant = ActiveVariant { scope: self.scope, text: self.start_text, @@ -172,14 +174,12 @@ where active_variant.reductions.push(variant_name); } - self.successes.push(( - SuccessfulParse { - text: active_variant.text, - reductions: active_variant.reductions, - value, - }, - Precedence(variant_precedence), - )); + self.successes.push(SuccessfulParse { + text: active_variant.text, + reductions: active_variant.reductions, + precedence: variant_precedence, + value, + }); tracing::trace!("success: {:?}", self.successes.last().unwrap()); } @@ -242,7 +242,7 @@ where .zip(0..) .all(|(s_j, j)| i == j || Self::is_preferable(s_i, s_j)) { - let (s_i, _) = self.successes.into_iter().skip(i).next().unwrap(); + let s_i = self.successes.into_iter().skip(i).next().unwrap(); // It's better to print this result alongside the main parsing section. drop(guard); tracing::trace!("best parse = `{:?}`", s_i); @@ -257,19 +257,13 @@ where ); } - fn is_preferable( - s_i: &(SuccessfulParse, Precedence), - s_j: &(SuccessfulParse, Precedence), - ) -> bool { - let (parse_i, prec_i) = s_i; - let (parse_j, prec_j) = s_j; - + fn is_preferable(s_i: &SuccessfulParse, s_j: &SuccessfulParse) -> bool { fn has_prefix(l1: &[T], l2: &[T]) -> bool { l1.len() > l2.len() && (0..l2.len()).all(|i| l1[i] == l2[i]) } - prec_i > prec_j - || (prec_i == prec_j && has_prefix(&parse_i.reductions, &parse_j.reductions)) + s_i.precedence > s_j.precedence + || (s_i.precedence == s_j.precedence && has_prefix(&s_i.reductions, &s_j.reductions)) } } @@ -686,6 +680,7 @@ where let SuccessfulParse { text, reductions, + precedence: _, value, } = op(self.scope, self.text)?; From ac30d3b428e89a62914259f0011f8faa56074996 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 7 Nov 2023 06:12:08 -0500 Subject: [PATCH 30/38] adopt final_fn crate This makes us panic safe, but it also just reads much more nicely. --- Cargo.lock | 7 +++++++ crates/formality-core/Cargo.toml | 1 + .../src/parse/parser/left_recursion.rs | 20 +++++++++---------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f218fa45..88f19571 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -529,6 +529,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +[[package]] +name = "final_fn" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe1fa59a36a9928c12b6c4e9658f8b84d43e7578cb6d532b069ce84dcd0afda6" + [[package]] name = "fn-error-context" version = "0.2.0" @@ -565,6 +571,7 @@ dependencies = [ "contracts", "env_logger", "expect-test", + "final_fn", "formality-macros", "lazy_static", "stacker", diff --git a/crates/formality-core/Cargo.toml b/crates/formality-core/Cargo.toml index 8908712c..54f3f6bd 100644 --- a/crates/formality-core/Cargo.toml +++ b/crates/formality-core/Cargo.toml @@ -20,6 +20,7 @@ tracing-tree = { version = "0.2" } formality-macros = { version = "0.1.0", path = "../formality-macros" } anyhow = "1.0.75" contracts = "0.6.3" +final_fn = "0.1.0" [dev-dependencies] expect-test = "1.4.1" diff --git a/crates/formality-core/src/parse/parser/left_recursion.rs b/crates/formality-core/src/parse/parser/left_recursion.rs index 8d781688..cd6012af 100644 --- a/crates/formality-core/src/parse/parser/left_recursion.rs +++ b/crates/formality-core/src/parse/parser/left_recursion.rs @@ -149,13 +149,11 @@ where }; } - let pop_stack_before_return = |r: ParseResult<'t, T>| { - STACK.with_borrow_mut(|stack| { - let top = stack.pop().unwrap(); - assert!(top.matches::(scope, text)); - }); - r - }; + // Pop the stack before we return + final_fn::final_fn!(STACK.with_borrow_mut(|stack| { + let top = stack.pop().unwrap(); + assert!(top.matches::(scope, text)); + })); // EXAMPLE: Consider this grammar // @@ -192,13 +190,13 @@ where let mut values = vec![]; match op() { Ok(v) => values.push(v), - Err(errs) => return pop_stack_before_return(Err(errs)), + Err(errs) => return Err(errs), }; // Check whether there was recursion to begin with. let observed = with_top!(|top| top.observed); if !observed { - return pop_stack_before_return(Ok(values.pop().unwrap())); // If not, we are done. + return Ok(values.pop().unwrap()); // If not, we are done. } // OK, this is the interesting case. We may be able to get a better parse. @@ -219,7 +217,7 @@ where // Invoke the operation. As noted above, if we get a failed parse NOW, // we know we already found the best result, so we can just use it. let Ok(value1) = op() else { - return pop_stack_before_return(Ok(values.pop().unwrap())); // If not, we are done. + return Ok(values.pop().unwrap()); // If not, we are done. }; tracing::trace!("left-recursive grammar yielded: value1 = {:?}", value1); @@ -230,7 +228,7 @@ where // succeeds, but we have to try again to see if there's a more complex // expression that can be produced (there isn't). if values.iter().any(|v| *v == value1) { - return pop_stack_before_return(Ok(values.pop().unwrap())); // If not, we are done. + return Ok(values.pop().unwrap()); // If not, we are done. } // Otherwise, we have to try again. From f7fe8f97b2f35171213f0c66e232181bb0de0aa8 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 7 Nov 2023 06:24:41 -0500 Subject: [PATCH 31/38] track the current state when we recurse Are we in Left position, right position, etc. (see docs) This will be used to guide parsing of variants so that we can properly handle recursion. --- crates/formality-core/src/parse/parser.rs | 86 +++++++++++++------ .../src/parse/parser/left_recursion.rs | 62 +++++++++++++ 2 files changed, 123 insertions(+), 25 deletions(-) diff --git a/crates/formality-core/src/parse/parser.rs b/crates/formality-core/src/parse/parser.rs index 829657cd..2d9c88d7 100644 --- a/crates/formality-core/src/parse/parser.rs +++ b/crates/formality-core/src/parse/parser.rs @@ -3,6 +3,7 @@ use std::str::FromStr; use crate::{ language::{CoreParameter, HasKind, Language}, + parse::parser::left_recursion::{CurrentState, LeftRight}, set, variable::CoreVariable, Downcast, DowncastFrom, Set, Upcast, @@ -56,8 +57,10 @@ pub struct ActiveVariant<'s, 't, L> where L: Language, { + precedence: Precedence, scope: &'s Scope, - text: &'t str, + start_text: &'t str, + current_text: &'t str, reductions: Vec<&'static str>, is_cast_variant: bool, } @@ -94,6 +97,8 @@ where nonterminal_name: &'static str, mut op: impl FnMut(&mut Self), ) -> ParseResult<'t, T> { + let text = skip_whitespace(text); + left_recursion::enter(scope, text, || { let tracing_span = tracing::span!( tracing::Level::TRACE, @@ -154,12 +159,8 @@ where let variant_precedence = Precedence(variant_precedence); - let mut active_variant = ActiveVariant { - scope: self.scope, - text: self.start_text, - reductions: vec![], - is_cast_variant: false, - }; + let mut active_variant = + ActiveVariant::new(variant_precedence, self.scope, self.start_text); let result = op(&mut active_variant); // Drop the guard here so that the "success" or "error" results appear outside the variant span. @@ -175,7 +176,7 @@ where } self.successes.push(SuccessfulParse { - text: active_variant.text, + text: active_variant.current_text, reductions: active_variant.reductions, precedence: variant_precedence, value, @@ -271,19 +272,50 @@ impl<'s, 't, L> ActiveVariant<'s, 't, L> where L: Language, { + fn new(precedence: Precedence, scope: &'s Scope, start_text: &'t str) -> Self { + let start_text = skip_whitespace(start_text); + Self { + precedence, + scope, + start_text, + current_text: start_text, + reductions: vec![], + is_cast_variant: false, + } + } + fn current_state(&self) -> CurrentState { + // Determine whether we are in Left or Right position -- Left means + // that we have not yet consumed any tokens. Right means that we have. + // See `LeftRight` type for more details. + // + // Subtle-ish: this comparison assumes there is no whitespace, + // but we establish that invariant in `Self::new`. + debug_assert_eq!(self.start_text, skip_whitespace(self.start_text)); + let left_right = if self.start_text == self.current_text { + LeftRight::Left + } else { + LeftRight::Right + }; + + CurrentState { + left_right, + precedence: self.precedence, + } + } + /// The current text remaining to be consumed. pub fn text(&self) -> &'t str { - self.text + self.current_text } /// Skips whitespace in the input, producing no reduction. pub fn skip_whitespace(&mut self) { - self.text = skip_whitespace(self.text); + self.current_text = skip_whitespace(self.current_text); } /// Skips a comma in the input, producing no reduction. pub fn skip_trailing_comma(&mut self) { - self.text = skip_trailing_comma(self.text); + self.current_text = skip_trailing_comma(self.current_text); } /// Marks this variant as an cast variant, @@ -366,7 +398,7 @@ where /// Consume next identifier-like string, requiring that it be equal to `expected`. #[tracing::instrument(level = "trace", ret)] pub fn expect_keyword(&mut self, expected: &str) -> Result<(), Set>> { - let text0 = self.text; + let text0 = self.current_text; match self.identifier_like_string() { Ok(ident) if &*ident == expected => Ok(()), _ => Err(ParseError::at( @@ -379,7 +411,7 @@ where /// Accepts any of the given keywords. #[tracing::instrument(level = "trace", ret)] pub fn expect_keyword_in(&mut self, expected: &[&str]) -> Result>> { - let text0 = self.text; + let text0 = self.current_text; match self.identifier_like_string() { Ok(ident) if expected.iter().any(|&kw| ident == kw) => Ok(ident), _ => Err(ParseError::at( @@ -398,7 +430,9 @@ where err: impl FnOnce(T) -> Set>, ) -> Result<(), Set>> { let mut this = ActiveVariant { - text: self.text, + precedence: self.precedence, + start_text: self.start_text, + current_text: self.current_text, reductions: vec![], scope: self.scope, is_cast_variant: false, @@ -420,7 +454,7 @@ where |p| p.expect_keyword_in(keywords), |ident| { ParseError::at( - self.text, + self.current_text, format!("expected identified, found keyword `{ident:?}`"), ) }, @@ -456,13 +490,15 @@ where op: impl FnOnce(&mut ActiveVariant<'_, 't, L>) -> R, ) -> R { let mut av = ActiveVariant { + precedence: self.precedence, scope: &scope, - text: self.text, + start_text: self.start_text, + current_text: self.current_text, reductions: vec![], is_cast_variant: false, }; let result = op(&mut av); - self.text = av.text; + self.current_text = av.current_text; self.reductions.extend(av.reductions); result } @@ -475,7 +511,7 @@ where |p| p.variable(), |var| { ParseError::at( - self.text, + self.current_text, format!("found unexpected in-scope variable {:?}", var), ) }, @@ -510,7 +546,7 @@ where { self.skip_whitespace(); let type_name = std::any::type_name::(); - let text0 = self.text; + let text0 = self.current_text; let id = self.identifier()?; match self.scope.lookup(&id) { Some(parameter) => match parameter.downcast() { @@ -535,7 +571,7 @@ where T: FromStr + std::fmt::Debug, { let description = std::any::type_name::(); - let text0 = self.text; + let text0 = self.current_text; let s = self.string(char::is_numeric, char::is_numeric, description)?; match T::from_str(&s) { Ok(t) => Ok(t), @@ -562,7 +598,7 @@ where ) -> Result>> { self.skip_whitespace(); let value; - (value, self.text) = op(self.text)?; + (value, self.current_text) = op(self.current_text)?; Ok(value) } @@ -590,7 +626,7 @@ where where T: CoreParse, { - let text0 = self.text; + let text0 = self.current_text; match self.nonterminal() { Ok(v) => Ok(Some(v)), Err(mut errs) => { @@ -598,7 +634,7 @@ where if errs.is_empty() { // If no errors consumed anything, then self.text // must not have advanced. - assert_eq!(skip_whitespace(text0), self.text); + assert_eq!(skip_whitespace(text0), self.current_text); Ok(None) } else { Err(errs) @@ -682,10 +718,10 @@ where reductions, precedence: _, value, - } = op(self.scope, self.text)?; + } = left_recursion::recurse(self.current_state(), || op(self.scope, self.current_text))?; // Adjust our point in the input text - self.text = text; + self.current_text = text; // Some value was produced, so there must have been a reduction assert!(!reductions.is_empty()); diff --git a/crates/formality-core/src/parse/parser/left_recursion.rs b/crates/formality-core/src/parse/parser/left_recursion.rs index cd6012af..2ee8deb5 100644 --- a/crates/formality-core/src/parse/parser/left_recursion.rs +++ b/crates/formality-core/src/parse/parser/left_recursion.rs @@ -14,6 +14,8 @@ use crate::{ parse::{ParseError, ParseResult, Scope, SuccessfulParse}, }; +use super::Precedence; + thread_local! { static STACK: RefCell> = Default::default() } @@ -26,6 +28,8 @@ struct StackEntry { /// The starting text: we use `*const` instead of `&'t str` start_text: *const str, + current_state: Option, + /// The TypeId of the type `T`. type_id: TypeId, @@ -37,6 +41,37 @@ struct StackEntry { observed: bool, } +#[allow(dead_code)] +pub(super) struct CurrentState { + pub left_right: LeftRight, + pub precedence: Precedence, +} + +/// Determines the kind of recursion the current variant +/// would have if it recursed. For example, given a grammar +/// with a variant +/// +/// ```text +/// E = E + E +/// ```` +/// +/// when `E` recurses, the first `E` is considered `Left` +/// because it occurs before any tokens have been consumed. +/// The second `E` is considered `Right`. +/// +/// This terminology is a bit weird if you have three recursions, +/// e.g. `E = E + E + E`. Really we should consider any further +/// recursions as `Other`, I suppose, but I'm too lazy to deal with that +/// right now. +#[allow(dead_code)] +pub(super) enum LeftRight { + /// Have not yet consumed any tokens. + Left, + + /// Consumed some tokens. + Right, +} + impl StackEntry { pub fn new(scope: &Scope, start_text: &str) -> Self where @@ -45,6 +80,7 @@ impl StackEntry { { Self { scope: erase_type(scope), + current_state: None, start_text, type_id: TypeId::of::(), value: None, @@ -70,6 +106,10 @@ impl StackEntry { { assert_eq!(self.start_text, start_text as *const str); assert_eq!(self.type_id, TypeId::of::()); + assert!( + self.current_state.is_some(), + "observed a stack frame with no current state (forgot to call `recuse`?)" + ); self.observed = true; @@ -236,6 +276,28 @@ where } } +pub fn recurse<'s, 't, R>(current_state: CurrentState, op: impl FnOnce() -> R) -> R { + STACK.with_borrow_mut(|stack| { + let top = stack.last_mut().unwrap(); + assert!( + top.current_state.is_none(), + "top of stack already has a current state" + ); + top.current_state = Some(current_state); + }); + + final_fn::final_fn!(STACK.with_borrow_mut(|stack| { + let top = stack.last_mut().unwrap(); + assert!( + top.current_state.is_some(), + "top of stack no longer has a current state" + ); + top.current_state = None; + })); + + op() +} + fn erase_type(s: &T) -> *const () { s as *const T as *const () } From afa8d8980c371a673dcdffd9416b7add1c856648 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 7 Nov 2023 06:47:16 -0500 Subject: [PATCH 32/38] track current text in current state --- crates/formality-core/src/parse/parser.rs | 1 + crates/formality-core/src/parse/parser/left_recursion.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/formality-core/src/parse/parser.rs b/crates/formality-core/src/parse/parser.rs index 2d9c88d7..8a4428e0 100644 --- a/crates/formality-core/src/parse/parser.rs +++ b/crates/formality-core/src/parse/parser.rs @@ -300,6 +300,7 @@ where CurrentState { left_right, precedence: self.precedence, + current_text: self.current_text, } } diff --git a/crates/formality-core/src/parse/parser/left_recursion.rs b/crates/formality-core/src/parse/parser/left_recursion.rs index 2ee8deb5..f62f9247 100644 --- a/crates/formality-core/src/parse/parser/left_recursion.rs +++ b/crates/formality-core/src/parse/parser/left_recursion.rs @@ -45,6 +45,7 @@ struct StackEntry { pub(super) struct CurrentState { pub left_right: LeftRight, pub precedence: Precedence, + pub current_text: *const str, } /// Determines the kind of recursion the current variant From 165cb7c2c2e3f186edb64cada665ba27af668db6 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 7 Nov 2023 10:34:15 -0500 Subject: [PATCH 33/38] introduce Precedence struct with associativity --- crates/formality-core/src/parse/parser.rs | 104 ++++++++++++++++-- crates/formality-macros/src/parse.rs | 2 +- crates/formality-macros/src/precedence.rs | 78 +++++++++++-- .../src/grammar/ty/parse_impls.rs | 25 +++-- tests/parser-torture-tests/precedence.rs | 3 +- 5 files changed, 181 insertions(+), 31 deletions(-) diff --git a/crates/formality-core/src/parse/parser.rs b/crates/formality-core/src/parse/parser.rs index 8a4428e0..1b70b776 100644 --- a/crates/formality-core/src/parse/parser.rs +++ b/crates/formality-core/src/parse/parser.rs @@ -35,8 +35,81 @@ where failures: Set>, } -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] -pub struct Precedence(usize); +/// The *precedence* of a variant determines how to manage +/// recursive invocations. +/// +/// The general rule is that an expression +/// with lower-precedence cannot be embedded +/// into an expression of higher-precedence. +/// So given `1 + 2 * 3`, the `+` cannot be a +/// (direct) child of the `*`, because `+` is +/// lower precedence. +/// +/// The tricky bit is what happens with *equal* +/// precedence. In that case, we have to consider +/// the [`Associativity`][] (see enum for details). +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Precedence { + level: usize, + associativity: Associativity, +} + +/// Determines what happens when you have equal precedence. +/// The result is dependent on whether you are embedding +/// in left position (i.e., a recurrence before having parsed any +/// tokens) or right position (a recurrence after parsed tokens). +/// So given `1 + 2 + 3`, `1 + 2` is a *left* occurrence of the second `+`. +/// And `2 + 3` is a *right* occurence of the first `+`. +/// +/// With `Associativity::Left`, equal precedence is allowed in left matches +/// but not right. So `1 + 2 + 3` parses as `(1 + 2) + 3`, as you would expect. +/// +/// With `Associativity::Right`, equal precedence is allowed in right matches +/// but not left. So `1 + 2 + 3` parses as `1 + (2 + 3)`. That's probably not what you wanted +/// for arithemetic expressions, but could be useful for (say) curried function types, +/// where `1 -> 2 -> 3` should parse as `1 -> (2 -> 3)`. +/// +/// With `Associativity::None`, equal precedence is not allowed anywhere, so +/// `1 + 2 + 3` is just an error and you have to explicitly add parentheses. +/// +/// Use `Precedence::default` for cases where precedence is not relevant. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Associativity { + Left, + Right, + None, +} + +impl Precedence { + /// Left associative with the given precedence level + pub fn left(level: usize) -> Self { + Self::new(level, Associativity::Left) + } + + /// Right associative with the given precedence level + pub fn right(level: usize) -> Self { + Self::new(level, Associativity::Right) + } + + /// Non-associative with the given precedence level + pub fn none(level: usize) -> Self { + Self::new(level, Associativity::None) + } + + /// Construct a new precedence. + fn new(level: usize, associativity: Associativity) -> Self { + Self { + level, + associativity, + } + } +} + +impl Default for Precedence { + fn default() -> Self { + Self::new(0, Associativity::None) + } +} /// The "active variant" struct is the main struct /// you will use if you are writing custom parsing logic. @@ -81,7 +154,7 @@ where mut op: impl FnMut(&mut ActiveVariant<'s, 't, L>) -> Result>>, ) -> ParseResult<'t, T> { Parser::multi_variant(scope, text, nonterminal_name, |parser| { - parser.parse_variant(nonterminal_name, 0, &mut op); + parser.parse_variant(nonterminal_name, Precedence::default(), &mut op); }) } @@ -126,7 +199,7 @@ where /// Shorthand for `parse_variant` where the parsing operation is to /// parse the type `V` and then upcast it to the desired result type. /// Also marks the variant as a cast variant. - pub fn parse_variant_cast(&mut self, variant_precedence: usize) + pub fn parse_variant_cast(&mut self, variant_precedence: Precedence) where V: CoreParse + Upcast, { @@ -146,19 +219,17 @@ where pub fn parse_variant( &mut self, variant_name: &'static str, - variant_precedence: usize, + variant_precedence: Precedence, op: impl FnOnce(&mut ActiveVariant<'s, 't, L>) -> Result>>, ) { let span = tracing::span!( tracing::Level::TRACE, "variant", name = variant_name, - variant_precedence = variant_precedence + ?variant_precedence, ); let guard = span.enter(); - let variant_precedence = Precedence(variant_precedence); - let mut active_variant = ActiveVariant::new(variant_precedence, self.scope, self.start_text); let result = op(&mut active_variant); @@ -263,8 +334,9 @@ where l1.len() > l2.len() && (0..l2.len()).all(|i| l1[i] == l2[i]) } - s_i.precedence > s_j.precedence - || (s_i.precedence == s_j.precedence && has_prefix(&s_i.reductions, &s_j.reductions)) + s_i.precedence.level > s_j.precedence.level + || (s_i.precedence.level == s_j.precedence.level + && has_prefix(&s_i.reductions, &s_j.reductions)) } } @@ -608,11 +680,21 @@ where pub fn nonterminal(&mut self) -> Result>> where T: CoreParse, - L: Language, { self.nonterminal_with(T::parse) } + /// Gives an error if `T` is parsable here. + pub fn reject_nonterminal(&mut self) -> Result<(), Set>> + where + T: CoreParse, + { + self.reject( + |p| p.nonterminal::(), + |value| ParseError::at(self.text(), format!("unexpected `{value:?}`")), + ) + } + /// Try to parse the current point as `T` and return `None` if there is nothing there. /// /// **NB:** If the parse partially succeeds, i.e., we are able to consume some tokens diff --git a/crates/formality-macros/src/parse.rs b/crates/formality-macros/src/parse.rs index f920d63a..65c45651 100644 --- a/crates/formality-macros/src/parse.rs +++ b/crates/formality-macros/src/parse.rs @@ -48,7 +48,7 @@ pub(crate) fn derive_parse_with_spec( for variant in s.variants() { let variant_name = Literal::string(&format!("{}::{}", s.ast().ident, variant.ast().ident)); let v = parse_variant(variant, external_spec, any_variable_variant)?; - let precedence = precedence(&variant.ast().attrs)?.literal(); + let precedence = precedence(&variant.ast().attrs)?.expr(); parse_variants.extend(quote_spanned!( variant.ast().ident.span() => __parser.parse_variant(#variant_name, #precedence, |__p| { #v }); diff --git a/crates/formality-macros/src/precedence.rs b/crates/formality-macros/src/precedence.rs index 4eee2f66..0350016f 100644 --- a/crates/formality-macros/src/precedence.rs +++ b/crates/formality-macros/src/precedence.rs @@ -1,16 +1,39 @@ use std::str::FromStr; -use proc_macro2::{Literal, TokenStream}; +use proc_macro2::{Ident, Literal, Span, TokenStream}; +use quote::quote; use syn::spanned::Spanned; -#[derive(Default, Debug)] -pub(crate) struct Precedence { - pub level: usize, +#[derive(Debug)] +pub(crate) enum Precedence { + Defaulted, + Parsed { + level: usize, + + /// Will be either the name of a construct method on the + /// `Parser::Associativity` type: `left``, `right``, or `none`. + associativity: Ident, + }, } impl Precedence { - pub fn literal(&self) -> Literal { - Literal::usize_unsuffixed(self.level) + pub fn expr(&self) -> TokenStream { + match self { + Precedence::Defaulted => quote!(formality_core::parse::Precedence::default()), + Precedence::Parsed { + level, + associativity, + } => { + let level = Literal::usize_unsuffixed(*level); + quote!(formality_core::parse::Precedence::#associativity(#level)) + } + } + } +} + +impl Default for Precedence { + fn default() -> Self { + Precedence::Defaulted } } @@ -18,7 +41,7 @@ impl syn::parse::Parse for Precedence { fn parse(input: syn::parse::ParseStream) -> syn::Result { let token_stream: TokenStream = input.parse()?; let span = token_stream.span(); - let mut tokens = token_stream.into_iter(); + let mut tokens = token_stream.into_iter().peekable(); let Some(token) = tokens.next() else { return Err(syn::Error::new(span, "precedence expected")); @@ -42,10 +65,49 @@ impl syn::parse::Parse for Precedence { } } + const VALID_ASSOCIATIVITIES: &[&str] = &["left", "right", "none"]; + let associativity = if let Some(comma_token) = tokens.next() { + match &comma_token { + proc_macro2::TokenTree::Punct(punct) if punct.as_char() == ',' => { + match tokens.next() { + Some(proc_macro2::TokenTree::Ident(ident)) + if VALID_ASSOCIATIVITIES + .iter() + .any(|a| *a == ident.to_string()) => + { + ident + } + + _ => { + return Err(syn::Error::new( + comma_token.span(), + &format!( + "expected valid associativity after comma, one of `{:?}`", + VALID_ASSOCIATIVITIES + ), + )); + } + } + } + + _ => { + return Err(syn::Error::new( + comma_token.span(), + "extra `,` followed by associativity", + )); + } + } + } else { + Ident::new("left", Span::call_site()) + }; + if let Some(token) = tokens.next() { return Err(syn::Error::new(token.span(), "extra tokens")); } - Ok(Precedence { level }) + Ok(Precedence::Parsed { + level, + associativity, + }) } } diff --git a/crates/formality-types/src/grammar/ty/parse_impls.rs b/crates/formality-types/src/grammar/ty/parse_impls.rs index 24db70e3..82765eb7 100644 --- a/crates/formality-types/src/grammar/ty/parse_impls.rs +++ b/crates/formality-types/src/grammar/ty/parse_impls.rs @@ -1,6 +1,8 @@ //! Handwritten parser impls. -use formality_core::parse::{ActiveVariant, CoreParse, ParseError, ParseResult, Parser, Scope}; +use formality_core::parse::{ + ActiveVariant, CoreParse, ParseError, ParseResult, Parser, Precedence, Scope, +}; use formality_core::Upcast; use formality_core::{seq, Set}; @@ -20,10 +22,13 @@ impl CoreParse for RigidTy { // Parse a `ScalarId` (and upcast it to `RigidTy`) with the highest // precedence. If someone writes `u8`, we always interpret it as a // scalar-id. - parser.parse_variant_cast::(1); + parser.parse_variant_cast::(Precedence::default()); // Parse something like `Id<...>` as an ADT. - parser.parse_variant("Adt", 0, |p| { + parser.parse_variant("Adt", Precedence::default(), |p| { + // Don't accept scalar-ids as Adt names. + p.reject_nonterminal::()?; + let name: AdtId = p.nonterminal()?; let parameters: Vec = parse_parameters(p)?; Ok(RigidTy { @@ -33,7 +38,7 @@ impl CoreParse for RigidTy { }); // Parse `&` - parser.parse_variant("Ref", 0, |p| { + parser.parse_variant("Ref", Precedence::default(), |p| { p.expect_char('&')?; let lt: Lt = p.nonterminal()?; let ty: Ty = p.nonterminal()?; @@ -44,7 +49,7 @@ impl CoreParse for RigidTy { .upcast()) }); - parser.parse_variant("RefMut", 0, |p| { + parser.parse_variant("RefMut", Precedence::default(), |p| { p.expect_char('&')?; p.expect_keyword("mut")?; let lt: Lt = p.nonterminal()?; @@ -55,7 +60,7 @@ impl CoreParse for RigidTy { }) }); - parser.parse_variant("Tuple", 0, |p| { + parser.parse_variant("Tuple", Precedence::default(), |p| { p.expect_char('(')?; p.reject_custom_keywords(&["alias", "rigid", "predicate"])?; let types: Vec = p.comma_nonterminal()?; @@ -74,7 +79,7 @@ impl CoreParse for RigidTy { impl CoreParse for AliasTy { fn parse<'t>(scope: &Scope, text: &'t str) -> ParseResult<'t, Self> { Parser::multi_variant(scope, text, "AliasTy", |parser| { - parser.parse_variant("associated type", 0, |p| { + parser.parse_variant("associated type", Precedence::default(), |p| { p.expect_char('<')?; let ty0: Ty = p.nonterminal()?; let () = p.expect_keyword("as")?; @@ -119,11 +124,11 @@ fn parse_parameters<'t>( impl CoreParse for ConstData { fn parse<'t>(scope: &Scope, text: &'t str) -> ParseResult<'t, Self> { Parser::multi_variant(scope, text, "ConstData", |parser| { - parser.parse_variant("Variable", 1, |p| p.variable()); + parser.parse_variant("Variable", Precedence::default(), |p| p.variable()); - parser.parse_variant_cast::(1); + parser.parse_variant_cast::(Precedence::default()); - parser.parse_variant("Int", 0, |p| { + parser.parse_variant("Int", Precedence::default(), |p| { let n: u128 = p.number()?; p.expect_char('_')?; let ty: Ty = p.nonterminal()?; diff --git a/tests/parser-torture-tests/precedence.rs b/tests/parser-torture-tests/precedence.rs index 43ca5f02..36ceccce 100644 --- a/tests/parser-torture-tests/precedence.rs +++ b/tests/parser-torture-tests/precedence.rs @@ -7,10 +7,11 @@ pub enum Expr { Id(Id), #[grammar($v0 + $v1)] + #[precedence(1)] Add(Arc, Arc), #[grammar($v0 * $v1)] - #[precedence(1)] + #[precedence(2)] Mul(Arc, Arc), } From 2b49e234ea071ccd0f8daf5232f60e807a0da373 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 7 Nov 2023 10:52:52 -0500 Subject: [PATCH 34/38] use reduction prefix to decide between variants We used to also consider precedence, but we are moving away from that approach. Remove some tests that no longer test a relevant edge case. --- crates/formality-core/src/parse/parser.rs | 4 +- tests/parser-torture-tests/precedence.rs | 68 ----------------------- 2 files changed, 1 insertion(+), 71 deletions(-) diff --git a/crates/formality-core/src/parse/parser.rs b/crates/formality-core/src/parse/parser.rs index 1b70b776..251241e9 100644 --- a/crates/formality-core/src/parse/parser.rs +++ b/crates/formality-core/src/parse/parser.rs @@ -334,9 +334,7 @@ where l1.len() > l2.len() && (0..l2.len()).all(|i| l1[i] == l2[i]) } - s_i.precedence.level > s_j.precedence.level - || (s_i.precedence.level == s_j.precedence.level - && has_prefix(&s_i.reductions, &s_j.reductions)) + has_prefix(&s_i.reductions, &s_j.reductions) } } diff --git a/tests/parser-torture-tests/precedence.rs b/tests/parser-torture-tests/precedence.rs index 36ceccce..85af2408 100644 --- a/tests/parser-torture-tests/precedence.rs +++ b/tests/parser-torture-tests/precedence.rs @@ -59,71 +59,3 @@ fn equal_precedence_panics() { "#]] .assert_debug_eq(&term); } - -#[test] -#[should_panic(expected = "extra tokens")] -fn higher_precedence_less_reductions_1() { - // Subtle: In this case, A has higher precedence, - // so even though we COULD parse the entire string, - // we prefer to just parse the first identifier, - // which results in a panic. - // - // In the past, we had a bug in our preference function - // that caused it to be order dependent, sometimes - // panicking and sometimes not. Hence we have a similar - // test with opposite order. - - #[term] - pub enum Root { - #[cast] - #[precedence(1)] - A(A), - - #[cast] - B(B), - } - - #[term($v0)] - pub struct A(Id); - - #[term($v0 $v1)] - pub struct B(A, Id); - - formality_core::id!(Id); - - let term: Root = crate::ptt::term("my String"); - expect_test::expect![[r#" - "#]] - .assert_debug_eq(&term); -} - -#[test] -#[should_panic(expected = "extra tokens")] -fn higher_precedence_less_reductions_2() { - // Same as `higher_precedence_less_reductions_1` but with - // opposite term order. See explanation in that function. - - #[term] - pub enum Root { - #[cast] - B(B), - - #[cast] - #[precedence(1)] - A(A), - } - - #[term($v0)] - pub struct A(Id); - - #[term($v0 $v1)] - pub struct B(A, Id); - - formality_core::id!(Id); - - let term: Root = crate::ptt::term("my String"); - expect_test::expect![[r#" - my String - "#]] - .assert_debug_eq(&term); -} From 61226f99ccd61b543a491adb328a173c787f53e6 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 7 Nov 2023 10:53:58 -0500 Subject: [PATCH 35/38] the default is now MAX priority We are going to start enforcing the invariant that an expression with priority N can only directly things of higher priority. In that case for something like this: ``` enum Expr { Id(Id), Literal(Literal), #[precedence(1, left)] Add(Expr, Expr), #[precedence(2, left)] Mul(Expr, Expr), } ``` you want Id/Literal to be max priority so they can be embedded into anything. --- crates/formality-core/src/parse/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/formality-core/src/parse/parser.rs b/crates/formality-core/src/parse/parser.rs index 251241e9..dea85e2f 100644 --- a/crates/formality-core/src/parse/parser.rs +++ b/crates/formality-core/src/parse/parser.rs @@ -107,7 +107,7 @@ impl Precedence { impl Default for Precedence { fn default() -> Self { - Self::new(0, Associativity::None) + Self::new(std::usize::MAX, Associativity::None) } } From 0b1120a0913db28deb3b014c939bf970e4587f59 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 7 Nov 2023 16:24:30 -0500 Subject: [PATCH 36/38] introduce proper precedence, associativity --- crates/formality-core/src/parse/parser.rs | 50 +++- .../src/parse/parser/left_recursion.rs | 272 ++++++++++++++---- .../src/grammar/ty/parse_impls.rs | 2 +- examples/formality-eg/grammar.rs | 6 +- tests/parser-torture-tests/main.rs | 1 + tests/parser-torture-tests/path.rs | 41 +++ tests/parser-torture-tests/precedence.rs | 15 +- 7 files changed, 310 insertions(+), 77 deletions(-) create mode 100644 tests/parser-torture-tests/path.rs diff --git a/crates/formality-core/src/parse/parser.rs b/crates/formality-core/src/parse/parser.rs index dea85e2f..96a63521 100644 --- a/crates/formality-core/src/parse/parser.rs +++ b/crates/formality-core/src/parse/parser.rs @@ -33,6 +33,7 @@ where nonterminal_name: &'static str, successes: Vec>, failures: Set>, + min_precedence_level: usize, } /// The *precedence* of a variant determines how to manage @@ -78,6 +79,7 @@ pub enum Associativity { Left, Right, None, + Both, } impl Precedence { @@ -98,16 +100,31 @@ impl Precedence { /// Construct a new precedence. fn new(level: usize, associativity: Associativity) -> Self { + // We require level to be STRICTLY LESS than usize::MAX + // so that we can always add 1. + assert!(level < std::usize::MAX); Self { level, - associativity, + associativity: associativity, } } } impl Default for Precedence { fn default() -> Self { - Self::new(std::usize::MAX, Associativity::None) + // Default precedence: + // + // Use MAX-1 because we sometimes try to add 1 when we detect recursion, and we don't want overflow. + // + // Use Right associativity because if you have a variant like `T = [T]` + // then you want to be able to (by default) embed arbitrary T in that recursive location. + // If you use LEFT or NONE, then this recursive T would have a minimum level of std::usize::MAX + // (and hence never be satisfied). + // + // Using RIGHT feels a bit weird here but seems to behave correctly all the time. + // It's tempting to add something like "Both" or "N/A" that would just not set a min + // prec level when there's recursion. + Self::new(std::usize::MAX - 1, Associativity::Both) } } @@ -172,7 +189,7 @@ where ) -> ParseResult<'t, T> { let text = skip_whitespace(text); - left_recursion::enter(scope, text, || { + left_recursion::enter(scope, text, |min_precedence_level| { let tracing_span = tracing::span!( tracing::Level::TRACE, "nonterminal", @@ -188,6 +205,7 @@ where nonterminal_name, successes: vec![], failures: set![], + min_precedence_level, }; op(&mut parser); @@ -230,6 +248,15 @@ where ); let guard = span.enter(); + if variant_precedence.level < self.min_precedence_level { + tracing::trace!( + "variant has precedence level {} which is below parser minimum of {}", + variant_precedence.level, + self.min_precedence_level, + ); + return; + } + let mut active_variant = ActiveVariant::new(variant_precedence, self.scope, self.start_text); let result = op(&mut active_variant); @@ -480,7 +507,7 @@ where } /// Accepts any of the given keywords. - #[tracing::instrument(level = "trace", ret)] + #[tracing::instrument(level = "trace", skip(self), ret)] pub fn expect_keyword_in(&mut self, expected: &[&str]) -> Result>> { let text0 = self.current_text; match self.identifier_like_string() { @@ -495,6 +522,7 @@ where /// Attempts to execute `op` and, if it successfully parses, then returns an error /// with the value (which is meant to be incorporated into a par) /// If `op` fails to parse then the result is `Ok`. + #[tracing::instrument(level = "trace", skip(self, op, err), ret)] pub fn reject( &self, op: impl Fn(&mut ActiveVariant<'s, 't, L>) -> Result>>, @@ -519,7 +547,6 @@ where /// Does not consume any input. /// You can this to implement positional keywords -- just before parsing an identifier or variable, /// you can invoke `reject_custom_keywords` to reject anything that you don't want to permit in this position. - #[tracing::instrument(level = "trace", ret)] pub fn reject_custom_keywords(&self, keywords: &[&str]) -> Result<(), Set>> { self.reject( |p| p.expect_keyword_in(keywords), @@ -534,7 +561,7 @@ where /// Extracts a string that meets the regex for an identifier /// (but it could also be a keyword). - #[tracing::instrument(level = "trace", ret)] + #[tracing::instrument(level = "trace", skip(self), ret)] pub fn identifier_like_string(&mut self) -> Result>> { self.string( |ch| matches!(ch, 'a'..='z' | 'A'..='Z' | '_'), @@ -547,7 +574,7 @@ where /// following the usual rules. **Disallows language keywords.** /// If you want to disallow additional keywords, /// see the `reject_custom_keywords` method. - #[tracing::instrument(level = "trace", ret)] + #[tracing::instrument(level = "trace", skip(self), ret)] pub fn identifier(&mut self) -> Result>> { self.reject_custom_keywords(L::KEYWORDS)?; self.identifier_like_string() @@ -610,7 +637,7 @@ where /// It also allows parsing where you use variables to stand for /// more complex parameters, which is kind of combining parsing /// and substitution and can be convenient in tests. - #[tracing::instrument(level = "trace", ret)] + #[tracing::instrument(level = "trace", skip(self), ret)] pub fn variable(&mut self) -> Result>> where R: Debug + DowncastFrom>, @@ -636,7 +663,7 @@ where } /// Extract a number from the input, erroring if the input does not start with a number. - #[tracing::instrument(level = "trace", ret)] + #[tracing::instrument(level = "trace", skip(self), ret)] pub fn number(&mut self) -> Result>> where T: FromStr + std::fmt::Debug, @@ -702,7 +729,7 @@ where /// because we consumed the open paren `(` from `T` but then encountered an error /// looking for the closing paren `)`. #[track_caller] - #[tracing::instrument(level = "Trace", ret)] + #[tracing::instrument(level = "Trace", skip(self), ret)] pub fn opt_nonterminal(&mut self) -> Result, Set>> where T: CoreParse, @@ -726,6 +753,7 @@ where /// Continue parsing instances of `T` while we can. /// This is a greedy parse. + #[tracing::instrument(level = "Trace", skip(self), ret)] pub fn many_nonterminal(&mut self) -> Result, Set>> where T: CoreParse, @@ -737,7 +765,7 @@ where Ok(result) } - #[tracing::instrument(level = "Trace", ret)] + #[tracing::instrument(level = "Trace", skip(self), ret)] pub fn delimited_nonterminal( &mut self, open: char, diff --git a/crates/formality-core/src/parse/parser/left_recursion.rs b/crates/formality-core/src/parse/parser/left_recursion.rs index f62f9247..862f77f4 100644 --- a/crates/formality-core/src/parse/parser/left_recursion.rs +++ b/crates/formality-core/src/parse/parser/left_recursion.rs @@ -7,11 +7,11 @@ //! `T` in our `thread_local!` and you can't have generic thread-local values. //! So we have to erase types. Annoying! -use std::{any::TypeId, cell::RefCell, fmt::Debug}; +use std::{any::TypeId, cell::RefCell, fmt::Debug, ops::ControlFlow}; use crate::{ language::Language, - parse::{ParseError, ParseResult, Scope, SuccessfulParse}, + parse::{parser::Associativity, ParseError, ParseResult, Scope, SuccessfulParse}, }; use super::Precedence; @@ -41,7 +41,7 @@ struct StackEntry { observed: bool, } -#[allow(dead_code)] +#[derive(Copy, Clone, Debug)] pub(super) struct CurrentState { pub left_right: LeftRight, pub precedence: Precedence, @@ -64,7 +64,7 @@ pub(super) struct CurrentState { /// e.g. `E = E + E + E`. Really we should consider any further /// recursions as `Other`, I suppose, but I'm too lazy to deal with that /// right now. -#[allow(dead_code)] +#[derive(Copy, Clone, Debug)] pub(super) enum LeftRight { /// Have not yet consumed any tokens. Left, @@ -89,7 +89,7 @@ impl StackEntry { } } - pub fn matches(&self, scope: &Scope, start_text: &str) -> bool + pub fn matches_start_state(&self, scope: &Scope, start_text: &str) -> bool where L: Language, T: Clone + 'static, @@ -100,39 +100,90 @@ impl StackEntry { scope == self.scope && start_text == self.start_text && self.type_id == type_id } + /// True if a call to parse a value of type `T` with the given scope scope/text + /// matches the current state of this stack frame -- this means that it is a recursive + /// call from this stack frame (directly or indirectly). + /// + /// # Example + /// + /// Consider this grammar: + /// + /// ```text + /// E = E + E + /// | ( E ) + /// | integer + /// ``` + /// + /// and sample input `"1 + (2 + 3)"`. + /// + /// We will start with the variant `E = E + E`. This will recursively try to parse + /// an `E` two times. + /// Each time it will invoke [`recurse`][] which will record the current text. + /// + /// The first time, the current text will be `"1 + (2 + 3)"`, same as the start text. + /// When we start parsing `E`, we'll invoke [`enter`][] and see a (left) match. + /// + /// Later, after we've parsed `1 + `, we'll try to parse another `E`, but with + /// the current text `"(2 + 3)"`. We'll again see a (right) match and eventually + /// reach the variant `E = ( E )`. + /// + /// This variant will recurse *again*. But this time the current text is `"2 + 3)"`. + /// This is not considered a recursive match. + pub fn matches_current_state<'t, L, T>( + &self, + scope: &Scope, + start_text: &'t str, + ) -> Option + where + L: Language, + T: 'static, + { + // Convert incoming scope/text to raw pointers. + let scope: *const () = erase_type(scope); + let type_id = TypeId::of::(); + + // Scope and type-id must match. + if scope != self.scope || type_id != self.type_id { + return None; + } + + // Start text must match *current* text of this frame. + let start_text: *const str = start_text; + let Some(current_state) = &self.current_state else { + panic!("observed a stack frame with no current state (forgot to call `recuse`?"); + }; + if start_text != current_state.current_text { + return None; + } + + // OK, we have a match! + Some(*current_state) + } + /// UNSAFE: Caller must guarantee that `self.value` pointer is valid. - pub unsafe fn observe<'t, T>(&mut self, start_text: &'t str) -> ParseResult<'t, T> + pub unsafe fn observe<'t, T>(&mut self, start_text: &'t str) -> Option> where T: Clone + 'static, { - assert_eq!(self.start_text, start_text as *const str); assert_eq!(self.type_id, TypeId::of::()); - assert!( - self.current_state.is_some(), - "observed a stack frame with no current state (forgot to call `recuse`?)" - ); + assert_eq!(self.start_text, start_text); // must be left-recursion + assert!(self.current_state.is_some()); self.observed = true; - match self.value { - Some(ptr) => { - let ptr = ptr as *const SuccessfulParse<'t, T>; - // UNSAFE: We rely on the caller to entry ptr is valid. - let ptr = unsafe { &*ptr }; - Ok(ptr.clone()) - } - None => Err(ParseError::at( - start_text, - format!("recursive grammar for `{}`", std::any::type_name::()), - )), - } + let ptr = self.value?; + let ptr = ptr as *const SuccessfulParse<'t, T>; + // UNSAFE: We rely on the caller to entry ptr is valid. + let ptr = unsafe { &*ptr }; + + Some(ptr.clone()) } } pub fn enter<'s, 't, L, T>( scope: &'s Scope, text: &'t str, - mut op: impl FnMut() -> ParseResult<'t, T>, + mut op: impl FnMut(usize) -> ParseResult<'t, T>, ) -> ParseResult<'t, T> where L: Language, @@ -146,37 +197,148 @@ where ); // First check whether we are already parsing this same text in this same scope as this same type. - let previous_result = STACK.with_borrow_mut(|stack| { - if let Some(entry) = stack - .iter_mut() - .find(|entry| entry.matches::(scope, text)) - { - // UNSAFE: We need to justify that `entry.value` will be valid. - // - // Each entry in `stack` corresponds to an active stack frame `F` on this thread - // and each entry in `stack` is only mutated by `F` - // - // The value in `entry.value` will either be `None` (in which case it is valid) - // or `Some(p)` where `p` is a pointer. - // - // `p` will have been assigned by `F` just before invoking `op()`. It is a reference - // to the last value in a vector owned by `F`. Since `F` is still active, that vector - // is still valid. The borrow to produce `p` is valid (by inspection) because there are no - // accesses to the vector until `op` completes - // (and, to arrive at this code, `op` has not yet completed). - unsafe { - let result = entry.observe::(text); - tracing::trace!("found left-recursive stack entry, result = {:?}", result); - Some(result) + let mut min_precedence_level = 0; + let return_value = STACK.with_borrow_mut(|stack| { + for entry in stack.iter_mut().rev() { + match entry.matches_current_state::(scope, text) { + // Keep searching. + None => (), + + // If this is left-recursion, then we will always return some value, but which value + // depends on a few factors. Consider `E = int | E + E | E * E` as our example. Because this + // is left-recursion, we know we are recursively parsing the first `E` in the `E + E` + // or `E * E` variant. + // + // The easy case is where there is no previous result. In that case, we return an error. + // This consitutes the 'base case' for a recursive grammar. + // We will only successfully parse the `int` case on that first round, but then we will try again. + // + // Otherwise, we have a prevous result for E, and we need to decide whether we can + // embed it into the variant we are currently parsing. Here we must consider the + // precedence of the variant we are parsing as well as the precedence level of the value + // we are attempting to reuse. The general rule is that you can embed higher precedence things + // into lower precedence things but not vice versa. + // + // If the current variant is left associative (as are all variants in our example), + // then we can accept values with the same precedence level as the variant or higher. + // So if the variant is `E * E` (precedence level = 2), we would only accept precedence + // level 2 (`E * E`) or 3 (`int`). If the variant were `E + E`, we could acccept anything. + // + // If the current variant is right or none associative, then we can accept values with + // strictly higher precedence level. So e.g. if `E + E` were level 1 and non-associative, + // then it would accept only things at level 2 or higher. + Some(CurrentState { + left_right: LeftRight::Left, + precedence: current_precedence, + .. + }) => { + let previous_result = unsafe { + // UNSAFE: See [1] below for justification. + entry.observe::(text) + }; + tracing::trace!( + "found left-recursive stack entry with precedence {:?}, previous_result = {:?}", + current_precedence, + previous_result + ); + let Some(previous_result) = previous_result else { + // Case 1: no previous value. + return ControlFlow::Break(Err(ParseError::at( + text, + format!( + "left-recursion on `{}` with no previous value", + std::any::type_name::() + ), + ))); + }; + + // If there is a previous value, check the precedence as described above. + let precedence_valid = match current_precedence.associativity { + Associativity::Left => { + previous_result.precedence.level >= current_precedence.level + } + Associativity::Right | Associativity::None => { + previous_result.precedence.level > current_precedence.level + } + Associativity::Both => true, + }; + tracing::trace!( + "precedence_valid = {}", + precedence_valid, + ); + if !precedence_valid { + return ControlFlow::Break(Err(ParseError::at( + text, + format!( + "left-recursion with invalid precedence \ + (current variant has precedence {:?}, previous value has level {})", + current_precedence, previous_result.precedence.level, + ), + ))); + } + + // Return the previous value. + return ControlFlow::Break(Ok(previous_result)); + + // [1] UNSAFE: We need to justify that `entry.value` will be valid. + // + // Each entry in `stack` corresponds to an active stack frame `F` on this thread + // and each entry in `stack` is only mutated by `F` + // + // The value in `entry.value` will either be `None` (in which case it is valid) + // or `Some(p)` where `p` is a pointer. + // + // `p` will have been assigned by `F` just before invoking `op()`. It is a reference + // to the last value in a vector owned by `F`. Since `F` is still active, that vector + // is still valid. The borrow to produce `p` is valid (by inspection) because there are no + // accesses to the vector until `op` completes + // (and, to arrive at this code, `op` has not yet completed). + } + + // If this is right-recursion, then we will not reuse the previous value, but we will + // consult the level of the variant to limit the variants we consider in this parse. + // Consider `E = int | E + E | E * E` as our example. Because this + // is right-recursion, we know we are recursively parsing the SECOND `E` in the `E + E` + // or `E * E` variant. + // + // If the current variant is left or none associative (as in our example), + // then we set the minimum precedence level to **one higher** than the variant's precedence. + // So if `E * E` is the current variant, we would only permit precedence level 2 (the `int` variant). + // So if `E + E` is the current variant, we would permit precedence level 1 or 2. + // + // If the current variant is right associative, then we can accept values with + // equal precedence. + Some(CurrentState { + left_right: LeftRight::Right, + precedence: current_precedence, + .. + }) => { + tracing::trace!( + "found right-recursive stack entry with precedence {:?}", + current_precedence, + ); + match current_precedence.associativity { + Associativity::Left | Associativity::None => { + min_precedence_level = current_precedence.level + 1; + } + Associativity::Right => { + min_precedence_level = current_precedence.level; + } + Associativity::Both => {} + }; + break; + } } - } else { - stack.push(StackEntry::new::(scope, text)); - None } + + stack.push(StackEntry::new::(scope, text)); + ControlFlow::Continue(()) }); - if let Some(previous_result) = previous_result { - return previous_result; + + if let ControlFlow::Break(return_value) = return_value { + return return_value; } + tracing::trace!("min_precedence_level = {}", min_precedence_level,); // Access the top stack frame. Use a macro because we don't support closures // that are generic over the return type. @@ -184,7 +346,7 @@ where (|$top:ident| $body:expr) => { STACK.with_borrow_mut(|stack| { let $top = stack.last_mut().unwrap(); - assert!($top.matches::(scope, text)); + assert!($top.matches_start_state::(scope, text)); $body }) }; @@ -193,7 +355,7 @@ where // Pop the stack before we return final_fn::final_fn!(STACK.with_borrow_mut(|stack| { let top = stack.pop().unwrap(); - assert!(top.matches::(scope, text)); + assert!(top.matches_start_state::(scope, text)); })); // EXAMPLE: Consider this grammar @@ -229,7 +391,7 @@ where // First round parse is a bit special, because if we get an error here, we can just return immediately, // as there is no base case to build from. let mut values = vec![]; - match op() { + match op(min_precedence_level) { Ok(v) => values.push(v), Err(errs) => return Err(errs), }; @@ -257,7 +419,7 @@ where // Invoke the operation. As noted above, if we get a failed parse NOW, // we know we already found the best result, so we can just use it. - let Ok(value1) = op() else { + let Ok(value1) = op(min_precedence_level) else { return Ok(values.pop().unwrap()); // If not, we are done. }; diff --git a/crates/formality-types/src/grammar/ty/parse_impls.rs b/crates/formality-types/src/grammar/ty/parse_impls.rs index 82765eb7..4c593196 100644 --- a/crates/formality-types/src/grammar/ty/parse_impls.rs +++ b/crates/formality-types/src/grammar/ty/parse_impls.rs @@ -18,7 +18,7 @@ use crate::rust::FormalityLang as Rust; // Implement custom parsing for rigid types. impl CoreParse for RigidTy { fn parse<'t>(scope: &Scope, text: &'t str) -> ParseResult<'t, Self> { - Parser::multi_variant(scope, text, "AliasTy", |parser| { + Parser::multi_variant(scope, text, "RigidTy", |parser| { // Parse a `ScalarId` (and upcast it to `RigidTy`) with the highest // precedence. If someone writes `u8`, we always interpret it as a // scalar-id. diff --git a/examples/formality-eg/grammar.rs b/examples/formality-eg/grammar.rs index 83f3ee64..a6dc25fc 100644 --- a/examples/formality-eg/grammar.rs +++ b/examples/formality-eg/grammar.rs @@ -84,17 +84,19 @@ pub enum Expr { StructLiteral(StructTy, Vec), #[grammar($v0 + $v1)] + #[precedence(1)] Add(Arc, Arc), #[grammar($v0 - $v1)] + #[precedence(1)] Sub(Arc, Arc), #[grammar($v0 * $v1)] - #[precedence(1)] + #[precedence(2)] Mul(Arc, Arc), #[grammar($v0 / $v1)] - #[precedence(1)] + #[precedence(2)] Div(Arc, Arc), #[grammar(($v0))] diff --git a/tests/parser-torture-tests/main.rs b/tests/parser-torture-tests/main.rs index 8a922c31..39836bd1 100644 --- a/tests/parser-torture-tests/main.rs +++ b/tests/parser-torture-tests/main.rs @@ -1,5 +1,6 @@ mod ambiguity; mod grammar; +mod path; mod precedence; formality_core::declare_language! { diff --git a/tests/parser-torture-tests/path.rs b/tests/parser-torture-tests/path.rs new file mode 100644 index 00000000..ddefc017 --- /dev/null +++ b/tests/parser-torture-tests/path.rs @@ -0,0 +1,41 @@ +use formality_core::{term, test}; +use std::sync::Arc; + +#[term] +pub enum Path { + #[cast] + Id(Id), + + #[grammar($v0 . $v1)] + Field(Arc, Id), + + #[grammar($v0 [ $v1 ])] + Index(Arc, Arc), +} + +formality_core::id!(Id); + +#[test] +fn path() { + let term: Path = crate::ptt::term("a.b[c.d].e"); + expect_test::expect![[r#" + Field( + Index( + Field( + Id( + a, + ), + b, + ), + Field( + Id( + c, + ), + d, + ), + ), + e, + ) + "#]] + .assert_debug_eq(&term); +} diff --git a/tests/parser-torture-tests/precedence.rs b/tests/parser-torture-tests/precedence.rs index 85af2408..d40931e2 100644 --- a/tests/parser-torture-tests/precedence.rs +++ b/tests/parser-torture-tests/precedence.rs @@ -1,4 +1,4 @@ -use formality_core::term; +use formality_core::{term, test}; use std::sync::Arc; #[term] @@ -39,22 +39,21 @@ fn mul_is_higher_precedence() { } #[test] -// FIXME #[should_panic(expected = "ambiguous parse")] -fn equal_precedence_panics() { +fn left_associative() { let term: Expr = crate::ptt::term("a + b + c"); expect_test::expect![[r#" Add( - Id( - a, - ), Add( Id( - b, + a, ), Id( - c, + b, ), ), + Id( + c, + ), ) "#]] .assert_debug_eq(&term); From 13fe33c6ed08623ed5b1284f3a1c86346cf983e4 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 9 Nov 2023 05:45:31 -0500 Subject: [PATCH 37/38] more thorough testing around associativity --- crates/formality-core/src/parse.rs | 2 +- .../parser-torture-tests/left_associative.rs | 159 ++++++++++++++++++ tests/parser-torture-tests/main.rs | 24 ++- .../parser-torture-tests/none_associative.rs | 138 +++++++++++++++ tests/parser-torture-tests/precedence.rs | 60 ------- .../parser-torture-tests/right_associative.rs | 159 ++++++++++++++++++ 6 files changed, 479 insertions(+), 63 deletions(-) create mode 100644 tests/parser-torture-tests/left_associative.rs create mode 100644 tests/parser-torture-tests/none_associative.rs delete mode 100644 tests/parser-torture-tests/precedence.rs create mode 100644 tests/parser-torture-tests/right_associative.rs diff --git a/crates/formality-core/src/parse.rs b/crates/formality-core/src/parse.rs index a410cb5c..598af984 100644 --- a/crates/formality-core/src/parse.rs +++ b/crates/formality-core/src/parse.rs @@ -168,7 +168,7 @@ pub type ParseResult<'t, T> = Result, Set> pub type TokenResult<'t, T> = Result<(T, &'t str), Set>>; /// Tracks the variables in scope at this point in parsing. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct Scope { bindings: Vec<(String, CoreParameter)>, } diff --git a/tests/parser-torture-tests/left_associative.rs b/tests/parser-torture-tests/left_associative.rs new file mode 100644 index 00000000..da96a83a --- /dev/null +++ b/tests/parser-torture-tests/left_associative.rs @@ -0,0 +1,159 @@ +use formality_core::{term, test}; +use std::sync::Arc; + +#[term] +pub enum Expr { + #[cast] + Id(Id), + + #[grammar($v0 + $v1)] + #[precedence(1)] + Add(Arc, Arc), + + #[grammar($v0 * $v1)] + #[precedence(2)] + Mul(Arc, Arc), +} + +formality_core::id!(Id); + +#[test] +fn add_mul() { + let term: Expr = crate::ptt::term("a + b * c"); + expect_test::expect![[r#" + Add( + Id( + a, + ), + Mul( + Id( + b, + ), + Id( + c, + ), + ), + ) + "#]] + .assert_debug_eq(&term); +} + +#[test] +fn mul_add() { + let term: Expr = crate::ptt::term("a * b + c"); + expect_test::expect![[r#" + Add( + Mul( + Id( + a, + ), + Id( + b, + ), + ), + Id( + c, + ), + ) + "#]] + .assert_debug_eq(&term); +} + +#[test] +fn add_add() { + let term: Expr = crate::ptt::term("a + b + c"); + expect_test::expect![[r#" + Add( + Add( + Id( + a, + ), + Id( + b, + ), + ), + Id( + c, + ), + ) + "#]] + .assert_debug_eq(&term); +} + +#[test] +fn mul_mul() { + let term: Expr = crate::ptt::term("a * b * c"); + expect_test::expect![[r#" + Mul( + Mul( + Id( + a, + ), + Id( + b, + ), + ), + Id( + c, + ), + ) + "#]] + .assert_debug_eq(&term); +} + +#[test] +fn mul_mul_mul() { + let term: Expr = crate::ptt::term("a * b * c * d"); + expect_test::expect![[r#" + Mul( + Mul( + Mul( + Id( + a, + ), + Id( + b, + ), + ), + Id( + c, + ), + ), + Id( + d, + ), + ) + "#]] + .assert_debug_eq(&term); +} + +#[test] +fn add_add_mul_add() { + let term: Expr = crate::ptt::term("a + b + c * d + e"); + expect_test::expect![[r#" + Add( + Add( + Add( + Id( + a, + ), + Id( + b, + ), + ), + Mul( + Id( + c, + ), + Id( + d, + ), + ), + ), + Id( + e, + ), + ) + "#]] + .assert_debug_eq(&term); +} diff --git a/tests/parser-torture-tests/main.rs b/tests/parser-torture-tests/main.rs index 39836bd1..c903ef45 100644 --- a/tests/parser-torture-tests/main.rs +++ b/tests/parser-torture-tests/main.rs @@ -1,7 +1,9 @@ mod ambiguity; mod grammar; +mod left_associative; +mod none_associative; mod path; -mod precedence; +mod right_associative; formality_core::declare_language! { mod ptt { @@ -20,8 +22,26 @@ formality_core::declare_language! { } } +/// Used to parse `text` when we expect some remainder +fn expect_remainder(text: &str) -> (T, &str) +where + T: CoreParse, +{ + match T::parse(&Default::default(), text) { + Ok(parse) => { + let (value, remainder) = parse.finish(); + assert!( + !remainder.is_empty(), + "expected to have remainder text, but parsed entire term `{text:?}`" + ); + (value, remainder) + } + Err(errs) => panic!("encountered unexpected parse error: {errs:#?}"), + } +} + // Default language for our crate -use formality_core::Fallible; +use formality_core::{parse::CoreParse, Fallible}; use ptt::FormalityLang; fn main() -> Fallible<()> { diff --git a/tests/parser-torture-tests/none_associative.rs b/tests/parser-torture-tests/none_associative.rs new file mode 100644 index 00000000..3b751e4e --- /dev/null +++ b/tests/parser-torture-tests/none_associative.rs @@ -0,0 +1,138 @@ +use formality_core::{term, test}; +use std::sync::Arc; + +use crate::expect_remainder; + +#[term] +pub enum Expr { + #[cast] + Id(Id), + + #[grammar($v0 + $v1)] + #[precedence(1, none)] + Add(Arc, Arc), + + #[grammar($v0 * $v1)] + #[precedence(2, none)] + Mul(Arc, Arc), +} + +formality_core::id!(Id); + +#[test] +fn add_mul() { + let term: Expr = crate::ptt::term("a + b * c"); + expect_test::expect![[r#" + Add( + Id( + a, + ), + Mul( + Id( + b, + ), + Id( + c, + ), + ), + ) + "#]] + .assert_debug_eq(&term); +} + +#[test] +fn mul_add() { + let term: Expr = crate::ptt::term("a * b + c"); + expect_test::expect![[r#" + Add( + Mul( + Id( + a, + ), + Id( + b, + ), + ), + Id( + c, + ), + ) + "#]] + .assert_debug_eq(&term); +} + +#[test] +fn add_add() { + let term = expect_remainder::("a + b + c"); + expect_test::expect![[r#" + ( + Add( + Id( + a, + ), + Id( + b, + ), + ), + " + c", + ) + "#]] + .assert_debug_eq(&term); +} + +#[test] +fn mul_mul() { + let term = expect_remainder::("a * b * c"); + expect_test::expect![[r#" + ( + Mul( + Id( + a, + ), + Id( + b, + ), + ), + " * c", + ) + "#]] + .assert_debug_eq(&term); +} + +#[test] +fn mul_mul_mul() { + let term = expect_remainder::("a * b * c * d"); + expect_test::expect![[r#" + ( + Mul( + Id( + a, + ), + Id( + b, + ), + ), + " * c * d", + ) + "#]] + .assert_debug_eq(&term); +} + +#[test] +fn add_add_mul_add() { + let term = expect_remainder::("a + b + c * d + e"); + expect_test::expect![[r#" + ( + Add( + Id( + a, + ), + Id( + b, + ), + ), + " + c * d + e", + ) + "#]] + .assert_debug_eq(&term); +} diff --git a/tests/parser-torture-tests/precedence.rs b/tests/parser-torture-tests/precedence.rs deleted file mode 100644 index d40931e2..00000000 --- a/tests/parser-torture-tests/precedence.rs +++ /dev/null @@ -1,60 +0,0 @@ -use formality_core::{term, test}; -use std::sync::Arc; - -#[term] -pub enum Expr { - #[cast] - Id(Id), - - #[grammar($v0 + $v1)] - #[precedence(1)] - Add(Arc, Arc), - - #[grammar($v0 * $v1)] - #[precedence(2)] - Mul(Arc, Arc), -} - -formality_core::id!(Id); - -#[test] -fn mul_is_higher_precedence() { - let term: Expr = crate::ptt::term("a + b * c"); - expect_test::expect![[r#" - Add( - Id( - a, - ), - Mul( - Id( - b, - ), - Id( - c, - ), - ), - ) - "#]] - .assert_debug_eq(&term); -} - -#[test] -fn left_associative() { - let term: Expr = crate::ptt::term("a + b + c"); - expect_test::expect![[r#" - Add( - Add( - Id( - a, - ), - Id( - b, - ), - ), - Id( - c, - ), - ) - "#]] - .assert_debug_eq(&term); -} diff --git a/tests/parser-torture-tests/right_associative.rs b/tests/parser-torture-tests/right_associative.rs new file mode 100644 index 00000000..966f3370 --- /dev/null +++ b/tests/parser-torture-tests/right_associative.rs @@ -0,0 +1,159 @@ +use formality_core::{term, test}; +use std::sync::Arc; + +#[term] +pub enum Expr { + #[cast] + Id(Id), + + #[grammar($v0 + $v1)] + #[precedence(1, right)] + Add(Arc, Arc), + + #[grammar($v0 * $v1)] + #[precedence(2, right)] + Mul(Arc, Arc), +} + +formality_core::id!(Id); + +#[test] +fn add_mul() { + let term: Expr = crate::ptt::term("a + b * c"); + expect_test::expect![[r#" + Add( + Id( + a, + ), + Mul( + Id( + b, + ), + Id( + c, + ), + ), + ) + "#]] + .assert_debug_eq(&term); +} + +#[test] +fn mul_add() { + let term: Expr = crate::ptt::term("a * b + c"); + expect_test::expect![[r#" + Add( + Mul( + Id( + a, + ), + Id( + b, + ), + ), + Id( + c, + ), + ) + "#]] + .assert_debug_eq(&term); +} + +#[test] +fn add_add() { + let term: Expr = crate::ptt::term("a + b + c"); + expect_test::expect![[r#" + Add( + Id( + a, + ), + Add( + Id( + b, + ), + Id( + c, + ), + ), + ) + "#]] + .assert_debug_eq(&term); +} + +#[test] +fn mul_mul() { + let term: Expr = crate::ptt::term("a * b * c"); + expect_test::expect![[r#" + Mul( + Id( + a, + ), + Mul( + Id( + b, + ), + Id( + c, + ), + ), + ) + "#]] + .assert_debug_eq(&term); +} + +#[test] +fn mul_mul_mul() { + let term: Expr = crate::ptt::term("a * b * c * d"); + expect_test::expect![[r#" + Mul( + Id( + a, + ), + Mul( + Id( + b, + ), + Mul( + Id( + c, + ), + Id( + d, + ), + ), + ), + ) + "#]] + .assert_debug_eq(&term); +} + +#[test] +fn add_add_mul_add() { + let term: Expr = crate::ptt::term("a + b + c * d + e"); + expect_test::expect![[r#" + Add( + Id( + a, + ), + Add( + Id( + b, + ), + Add( + Mul( + Id( + c, + ), + Id( + d, + ), + ), + Id( + e, + ), + ), + ), + ) + "#]] + .assert_debug_eq(&term); +} From ae6c33c236a97c1d8fca2cebcb665b3cb0e99ff0 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 9 Nov 2023 05:52:09 -0500 Subject: [PATCH 38/38] document ambiguity and precedence a bit more --- book/src/formality_core/parse.md | 31 +++++++++++-------- .../parser-torture-tests/left_associative.rs | 2 ++ tests/parser-torture-tests/path.rs | 2 ++ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/book/src/formality_core/parse.md b/book/src/formality_core/parse.md index 6798ec04..f728ff82 100644 --- a/book/src/formality_core/parse.md +++ b/book/src/formality_core/parse.md @@ -19,28 +19,33 @@ struct MyEnum { } ``` -### Ambiguity and precedence +### Ambiguity and greedy parsing -When parsing an enum there will be multiple possibilities. We will attempt to parse them all. If more than one succeeds, the parser will attempt to resolve the ambiguity. Ambiguity can be resolved in two ways: +When parsing an enum there will be multiple possibilities. We will attempt to parse them all. If more than one succeeds, the parser will attempt to resolve the ambiguity by looking for the **longest match**. However, we don't just consider the number of characters, we look for a **reduction prefix**: -* Explicit precedence: By default, every variant has precedence 0, but you can override this by annotating variants with `#[precedence(N)]` (where `N` is some integer). This will override the precedence for that variant. Variants with higher precedences are preferred. -* Reduction prefix: When parsing, we track the list of things we had to parse. If there are two variants at the same precedence level, but one of them had to parse strictly more things than the other and in the same way, we'll prefer the longer one. So for example if one variant parsed a `Ty` and the other parsed a `Ty Ty`, we'd take the `Ty Ty`. +* When parsing, we track the list of things we had to parse. If there are two variants at the same precedence level, but one of them had to parse strictly more things than the other and in the same way, we'll prefer the longer one. So for example if one variant parsed a `Ty` and the other parsed a `Ty Ty`, we'd take the `Ty Ty`. * When considering whether a reduction is "significant", we take casts into account. See `ActiveVariant::mark_as_cast_variant` for a more detailed explanation and set of examples. -Otherwise, the parser will panic and report ambiguity. The parser panics rather than returning an error because ambiguity doesn't mean that there is no way to parse the given text as the nonterminal -- rather that there are multiple ways. Errors mean that the text does not match the grammar for that nonterminal. +### Precedence and left-recursive grammars -### Left-recursive grammars - -We permit left recursive grammars like: +We support left-recursive grammars like this one from the `parse-torture-tests`: +```rust +{{#include ../../../tests/parser-torture-tests/src/path.rs:path}} ``` -Expr = Expr + Expr - | integer + +We also support ambiguous grammars. For example, you can code up arithmetic expressions like this: + + +```rust +{{#include ../../../tests/parser-torture-tests/src/left_associative.rs:Expr}} ``` -We *always* bias towards greedy parses, so `a + b + c` parses as `(a + b) + c`. -This might occasionally not be what you want. -Sorry. +When specifying the `#[precedence]` of a variant, the default is left-associativity, which can be written more explicitly as `#[precedence(L, left)]`. If you prefer, you can specify right-associativity (`#[precedence(L, right)]`) or non-associativity `#[precedence(L, none)]`. This affects how things of the same level are parsed: + +* `1 + 1 + 1` when left-associative is `(1 + 1) + 1` +* `1 + 1 + 1` when right-associative is `1 + (1 + 1)` +* `1 + 1 + 1` when none-associative is an error. ### Symbols diff --git a/tests/parser-torture-tests/left_associative.rs b/tests/parser-torture-tests/left_associative.rs index da96a83a..61896962 100644 --- a/tests/parser-torture-tests/left_associative.rs +++ b/tests/parser-torture-tests/left_associative.rs @@ -1,6 +1,7 @@ use formality_core::{term, test}; use std::sync::Arc; +// ANCHOR: Expr #[term] pub enum Expr { #[cast] @@ -16,6 +17,7 @@ pub enum Expr { } formality_core::id!(Id); +// ANCHOR_END: Expr #[test] fn add_mul() { diff --git a/tests/parser-torture-tests/path.rs b/tests/parser-torture-tests/path.rs index ddefc017..1862f288 100644 --- a/tests/parser-torture-tests/path.rs +++ b/tests/parser-torture-tests/path.rs @@ -1,6 +1,7 @@ use formality_core::{term, test}; use std::sync::Arc; +// ANCHOR: path #[term] pub enum Path { #[cast] @@ -14,6 +15,7 @@ pub enum Path { } formality_core::id!(Id); +// ANCHOR_END: path #[test] fn path() {