Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: turbofish in struct pattern #5616

Merged
merged 7 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions compiler/noirc_frontend/src/ast/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,18 @@ pub struct PathSegment {
pub span: Span,
}

impl PathSegment {
/// Returns the span where turbofish happen. For example:
///
/// foo::<T>
/// ~^^^^
///
/// Returns an empty span at the end of `foo` if there's no turbofish.
pub fn turbofish_span(&self) -> Span {
Span::from(self.ident.span().end()..self.span.end())
}
}

impl From<Ident> for PathSegment {
fn from(ident: Ident) -> PathSegment {
let span = ident.span();
Expand Down
23 changes: 7 additions & 16 deletions compiler/noirc_frontend/src/elaborator/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,23 +430,14 @@ impl<'context> Elaborator<'context> {
}
};

let struct_generics = if let Some(turbofish_generics) = &last_segment.generics {
if turbofish_generics.len() == struct_generics.len() {
let struct_type = r#type.borrow();
self.resolve_turbofish_generics(&struct_type.generics, turbofish_generics.clone())
} else {
self.push_err(TypeCheckError::GenericCountMismatch {
item: format!("struct {}", last_segment.ident),
expected: struct_generics.len(),
found: turbofish_generics.len(),
span: Span::from(last_segment.ident.span().end()..last_segment.span.end()),
});
let turbofish_span = last_segment.turbofish_span();

struct_generics
}
} else {
struct_generics
};
let struct_generics = self.resolve_struct_turbofish_generics(
&r#type.borrow(),
struct_generics,
last_segment.generics,
turbofish_span,
);

let struct_type = r#type.clone();
let generics = struct_generics.clone();
Expand Down
41 changes: 39 additions & 2 deletions compiler/noirc_frontend/src/elaborator/patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,12 @@ impl<'context> Elaborator<'context> {
mutable: Option<Span>,
new_definitions: &mut Vec<HirIdent>,
) -> HirPattern {
let name_span = name.last_ident().span();
let is_self_type = name.last_ident().is_self_type_name();
let exclude_last_segment = true;
self.check_unsupported_turbofish_usage(&name, exclude_last_segment);

let last_segment = name.last_segment();
let name_span = last_segment.ident.span();
let is_self_type = last_segment.ident.is_self_type_name();

let error_identifier = |this: &mut Self| {
// Must create a name here to return a HirPattern::Identifier. Allowing
Expand All @@ -178,6 +182,15 @@ impl<'context> Elaborator<'context> {
}
};

let turbofish_span = last_segment.turbofish_span();

let generics = self.resolve_struct_turbofish_generics(
&struct_type.borrow(),
generics,
last_segment.generics,
turbofish_span,
);

let actual_type = Type::Struct(struct_type.clone(), generics);
let location = Location::new(span, self.file);

Expand Down Expand Up @@ -426,6 +439,30 @@ impl<'context> Elaborator<'context> {
})
}

pub(super) fn resolve_struct_turbofish_generics(
&mut self,
struct_type: &StructType,
generics: Vec<Type>,
unresolved_turbofish: Option<Vec<UnresolvedType>>,
span: Span,
) -> Vec<Type> {
let Some(turbofish_generics) = unresolved_turbofish else {
return generics;
};

if turbofish_generics.len() != generics.len() {
self.push_err(TypeCheckError::GenericCountMismatch {
item: format!("struct {}", struct_type.name),
expected: generics.len(),
found: turbofish_generics.len(),
span,
});
return generics;
}

self.resolve_turbofish_generics(&struct_type.generics, turbofish_generics)
}

pub(super) fn resolve_turbofish_generics(
&mut self,
generics: &[ResolvedGeneric],
Expand Down
3 changes: 1 addition & 2 deletions compiler/noirc_frontend/src/elaborator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1623,8 +1623,7 @@ impl<'context> Elaborator<'context> {
}

if segment.generics.is_some() {
// From "foo::<T>", create a span for just "::<T>"
let span = Span::from(segment.ident.span().end()..segment.span.end());
let span = segment.turbofish_span();
self.push_err(TypeCheckError::UnsupportedTurbofishUsage { span });
}
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/noirc_frontend/src/parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@

use chumsky::prelude::*;
use iter_extended::vecmap;
use lalrpop_util::lalrpop_mod;

Check warning on line 53 in compiler/noirc_frontend/src/parser/parser.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (lalrpop)

Check warning on line 53 in compiler/noirc_frontend/src/parser/parser.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (lalrpop)
use noirc_errors::{Span, Spanned};

mod assertion;
Expand All @@ -64,8 +64,8 @@
mod traits;
mod types;

// synthesized by LALRPOP

Check warning on line 67 in compiler/noirc_frontend/src/parser/parser.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (LALRPOP)
lalrpop_mod!(pub noir_parser);

Check warning on line 68 in compiler/noirc_frontend/src/parser/parser.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (lalrpop)

#[cfg(test)]
mod test_helpers;
Expand All @@ -90,12 +90,12 @@

if cfg!(feature = "experimental_parser") {
for parsed_item in &parsed_module.items {
if lalrpop_parser_supports_kind(&parsed_item.kind) {

Check warning on line 93 in compiler/noirc_frontend/src/parser/parser.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (lalrpop)
match &parsed_item.kind {
ItemKind::Import(parsed_use_tree) => {
prototype_parse_use_tree(Some(parsed_use_tree), source_program);
}
// other kinds prevented by lalrpop_parser_supports_kind

Check warning on line 98 in compiler/noirc_frontend/src/parser/parser.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (lalrpop)
_ => unreachable!(),
}
}
Expand All @@ -112,7 +112,7 @@
}

let mut lexer = Lexer::new(input);
lexer = lexer.skip_whitespaces(false);

Check warning on line 115 in compiler/noirc_frontend/src/parser/parser.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (whitespaces)
let mut errors = Vec::new();

// NOTE: this is a hack to get the references working
Expand Down Expand Up @@ -559,7 +559,7 @@
.separated_by(just(Token::Comma))
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace));

let struct_pattern = path()
let struct_pattern = path(super::parse_type())
.then(struct_pattern_fields)
.map_with_span(|(typename, fields), span| Pattern::Struct(typename, fields, span));

Expand Down Expand Up @@ -1130,7 +1130,7 @@
.allow_trailing()
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace));

path().then(args).map(ExpressionKind::constructor)
path(super::parse_type()).then(args).map(ExpressionKind::constructor)
}

fn constructor_field<P>(expr_parser: P) -> impl NoirParser<(Ident, Expression)>
Expand Down
23 changes: 14 additions & 9 deletions compiler/noirc_frontend/src/parser/parser/path.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::ast::{Path, PathKind, PathSegment};
use crate::ast::{Path, PathKind, PathSegment, UnresolvedType};
use crate::parser::NoirParser;

use crate::token::{Keyword, Token};
Expand All @@ -8,8 +8,10 @@ use chumsky::prelude::*;
use super::keyword;
use super::primitives::{path_segment, path_segment_no_turbofish};

pub(super) fn path() -> impl NoirParser<Path> {
path_inner(path_segment())
pub(super) fn path<'a>(
type_parser: impl NoirParser<UnresolvedType> + 'a,
) -> impl NoirParser<Path> + 'a {
path_inner(path_segment(type_parser))
}

pub(super) fn path_no_turbofish() -> impl NoirParser<Path> {
Expand Down Expand Up @@ -40,13 +42,16 @@ fn empty_path() -> impl NoirParser<Path> {
}

pub(super) fn maybe_empty_path() -> impl NoirParser<Path> {
path().or(empty_path())
path_no_turbofish().or(empty_path())
}

#[cfg(test)]
mod test {
use super::*;
use crate::parser::parser::test_helpers::{parse_all_failing, parse_with};
use crate::parser::{
parse_type,
parser::test_helpers::{parse_all_failing, parse_with},
};

#[test]
fn parse_path() {
Expand All @@ -59,13 +64,13 @@ mod test {
];

for (src, expected_segments) in cases {
let path: Path = parse_with(path(), src).unwrap();
let path: Path = parse_with(path(parse_type()), src).unwrap();
for (segment, expected) in path.segments.into_iter().zip(expected_segments) {
assert_eq!(segment.ident.0.contents, expected);
}
}

parse_all_failing(path(), vec!["std::", "::std", "std::hash::", "foo::1"]);
parse_all_failing(path(parse_type()), vec!["std::", "::std", "std::hash::", "foo::1"]);
}

#[test]
Expand All @@ -78,12 +83,12 @@ mod test {
];

for (src, expected_path_kind) in cases {
let path = parse_with(path(), src).unwrap();
let path = parse_with(path(parse_type()), src).unwrap();
assert_eq!(path.kind, expected_path_kind);
}

parse_all_failing(
path(),
path(parse_type()),
vec!["crate", "crate::std::crate", "foo::bar::crate", "foo::dep"],
);
}
Expand Down
14 changes: 9 additions & 5 deletions compiler/noirc_frontend/src/parser/parser/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ pub(super) fn token_kind(token_kind: TokenKind) -> impl NoirParser<Token> {
})
}

pub(super) fn path_segment() -> impl NoirParser<PathSegment> {
ident()
.then(turbofish(super::parse_type()))
.map_with_span(|(ident, generics), span| PathSegment { ident, generics, span })
pub(super) fn path_segment<'a>(
type_parser: impl NoirParser<UnresolvedType> + 'a,
) -> impl NoirParser<PathSegment> + 'a {
ident().then(turbofish(type_parser)).map_with_span(|(ident, generics), span| PathSegment {
ident,
generics,
span,
})
}

pub(super) fn path_segment_no_turbofish() -> impl NoirParser<PathSegment> {
Expand Down Expand Up @@ -96,7 +100,7 @@ pub(super) fn turbofish<'a>(
}

pub(super) fn variable() -> impl NoirParser<ExpressionKind> {
path().map(ExpressionKind::Variable)
path(super::parse_type()).map(ExpressionKind::Variable)
asterite marked this conversation as resolved.
Show resolved Hide resolved
}

pub(super) fn variable_no_turbofish() -> impl NoirParser<ExpressionKind> {
Expand Down
71 changes: 71 additions & 0 deletions compiler/noirc_frontend/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2309,7 +2309,7 @@
}

#[test]
fn underflowing_u8() {

Check warning on line 2312 in compiler/noirc_frontend/src/tests.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (underflowing)
let src = r#"
fn main() {
let _: u8 = -1;
Expand Down Expand Up @@ -2347,7 +2347,7 @@
}

#[test]
fn underflowing_i8() {

Check warning on line 2350 in compiler/noirc_frontend/src/tests.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (underflowing)
let src = r#"
fn main() {
let _: i8 = -129;
Expand Down Expand Up @@ -2610,3 +2610,74 @@
CompilationError::TypeError(TypeCheckError::UnsupportedTurbofishUsage { .. }),
));
}

#[test]
asterite marked this conversation as resolved.
Show resolved Hide resolved
fn turbofish_in_struct_pattern() {
let src = r#"
struct Foo<T> {
x: T
}

fn main() {
let value: Field = 0;
let Foo::<Field> { x } = Foo { x: value };
let _ = x;
}
"#;
assert_no_errors(src);
}

#[test]
fn turbofish_in_struct_pattern_errors_if_type_mismatch() {
let src = r#"
struct Foo<T> {
x: T
}

fn main() {
let value: Field = 0;
let Foo::<i32> { x } = Foo { x: value };
let _ = x;
}
"#;

let errors = get_program_errors(src);
assert_eq!(errors.len(), 1);

let CompilationError::TypeError(TypeCheckError::TypeMismatchWithSource { .. }) = &errors[0].0
else {
panic!("Expected a type mismatch error, got {:?}", errors[0].0);
};
}

#[test]
fn turbofish_in_struct_pattern_generic_count_mismatch() {
let src = r#"
struct Foo<T> {
x: T
}

fn main() {
let value = 0;
let Foo::<i32, i64> { x } = Foo { x: value };
let _ = x;
}
"#;

let errors = get_program_errors(src);
assert_eq!(errors.len(), 1);

let CompilationError::TypeError(TypeCheckError::GenericCountMismatch {
item,
expected,
found,
..
}) = &errors[0].0
else {
panic!("Expected a generic count mismatch error, got {:?}", errors[0].0);
};

assert_eq!(item, "struct Foo");
assert_eq!(*expected, 1);
assert_eq!(*found, 2);
}
Loading