Skip to content

Commit

Permalink
feat: turbofish in struct pattern (#5616)
Browse files Browse the repository at this point in the history
# Description

## Problem

Part of #5584

## Summary

Allows parsing turbofish in a struct pattern path segments, then uses
those turbofish generics for type binding/inference.

## Additional Context

None.

## Documentation

Check one:
- [x] No documentation needed.
- [ ] Documentation included in this PR.
- [ ] **[For Experimental Features]** Documentation to be submitted in a
separate PR.

# PR Checklist\*

- [x] I have tested the changes locally.
- [x] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
asterite authored Jul 29, 2024
1 parent 5824785 commit b3c408b
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 36 deletions.
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 @@ -429,23 +429,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 @@ -557,7 +557,7 @@ fn pattern() -> impl NoirParser<Pattern> {
.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 @@ -1128,7 +1128,7 @@ fn constructor(expr_parser: impl ExprParser) -> impl NoirParser<ExpressionKind>
.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)
}

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 @@ -2610,3 +2610,74 @@ fn turbofish_in_middle_of_variable_unsupported_yet() {
CompilationError::TypeError(TypeCheckError::UnsupportedTurbofishUsage { .. }),
));
}

#[test]
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);
}

0 comments on commit b3c408b

Please sign in to comment.