Skip to content

Commit

Permalink
feat: turbofish operator in struct constructor (#5607)
Browse files Browse the repository at this point in the history
# Description

## Problem

Part of #5584

## Summary

This adds support for things like `let foo = Foo::<i32> { x }`.

## 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 26, 2024
1 parent e8bb341 commit 106abd7
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 20 deletions.
28 changes: 23 additions & 5 deletions compiler/noirc_frontend/src/elaborator/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ impl<'context> Elaborator<'context> {
&mut object,
);

self.resolve_turbofish_generics(&func_id, method_call.generics, span)
self.resolve_function_turbofish_generics(&func_id, method_call.generics, span)
} else {
None
};
Expand Down Expand Up @@ -408,12 +408,12 @@ impl<'context> Elaborator<'context> {
&mut self,
constructor: ConstructorExpression,
) -> (HirExpression, Type) {
let exclude_last_segment = false;
let exclude_last_segment = true;
self.check_unsupported_turbofish_usage(&constructor.type_name, exclude_last_segment);

let span = constructor.type_name.span();
let last_segment = constructor.type_name.last_ident();
let is_self_type = last_segment.is_self_type_name();
let last_segment = constructor.type_name.last_segment();
let is_self_type = last_segment.ident.is_self_type_name();

let (r#type, struct_generics) = if let Some(struct_id) = constructor.struct_type {
let typ = self.interner.get_struct(struct_id);
Expand All @@ -430,6 +430,24 @@ 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()),
});

struct_generics
}
} else {
struct_generics
};

let struct_type = r#type.clone();
let generics = struct_generics.clone();

Expand All @@ -444,7 +462,7 @@ impl<'context> Elaborator<'context> {
});

let struct_id = struct_type.borrow().id;
let reference_location = Location::new(last_segment.span(), self.file);
let reference_location = Location::new(last_segment.ident.span(), self.file);
self.interner.add_struct_reference(struct_id, reference_location, is_self_type);

(expr, Type::Struct(struct_type, generics))
Expand Down
28 changes: 18 additions & 10 deletions compiler/noirc_frontend/src/elaborator/patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::{
},
macros_api::{HirExpression, Ident, Path, Pattern},
node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, GlobalId, TraitImplKind},
Shared, StructType, Type, TypeBindings,
ResolvedGeneric, Shared, StructType, Type, TypeBindings,
};

use super::{Elaborator, ResolverMeta};
Expand Down Expand Up @@ -404,28 +404,36 @@ impl<'context> Elaborator<'context> {
}

/// Resolve generics using the expected kinds of the function we are calling
pub(super) fn resolve_turbofish_generics(
pub(super) fn resolve_function_turbofish_generics(
&mut self,
func_id: &FuncId,
unresolved_turbofish: Option<Vec<UnresolvedType>>,
span: Span,
) -> Option<Vec<Type>> {
let direct_generics = self.interner.function_meta(func_id).direct_generics.clone();

unresolved_turbofish.map(|option_inner| {
if option_inner.len() != direct_generics.len() {
unresolved_turbofish.map(|unresolved_turbofish| {
if unresolved_turbofish.len() != direct_generics.len() {
let type_check_err = TypeCheckError::IncorrectTurbofishGenericCount {
expected_count: direct_generics.len(),
actual_count: option_inner.len(),
actual_count: unresolved_turbofish.len(),
span,
};
self.push_err(type_check_err);
}

let generics_with_types = direct_generics.iter().zip(option_inner);
vecmap(generics_with_types, |(generic, unresolved_type)| {
self.resolve_type_inner(unresolved_type, &generic.kind)
})
self.resolve_turbofish_generics(&direct_generics, unresolved_turbofish)
})
}

pub(super) fn resolve_turbofish_generics(
&mut self,
generics: &[ResolvedGeneric],
turbofish_generics: Vec<UnresolvedType>,
) -> Vec<Type> {
let generics_with_types = generics.iter().zip(turbofish_generics);
vecmap(generics_with_types, |(generic, unresolved_type)| {
self.resolve_type_inner(unresolved_type, &generic.kind)
})
}

Expand All @@ -446,7 +454,7 @@ impl<'context> Elaborator<'context> {
// and if the turbofish operator was used.
let generics = definition_kind.and_then(|definition_kind| match &definition_kind {
DefinitionKind::Function(function) => {
self.resolve_turbofish_generics(function, unresolved_turbofish, span)
self.resolve_function_turbofish_generics(function, unresolved_turbofish, span)
}
_ => None,
});
Expand Down
35 changes: 31 additions & 4 deletions compiler/noirc_frontend/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2539,25 +2539,52 @@ fn trait_constraint_on_tuple_type() {
}

#[test]
fn turbofish_in_constructor_unsupported_yet() {
fn turbofish_in_constructor_generics_mismatch() {
let src = r#"
struct Foo<T> {
x: T
}
fn main() {
let _ = Foo::<i32> { x: 1 };
let _ = Foo::<i32, i64> { x: 1 };
}
"#;

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

assert!(matches!(
errors[0].0,
CompilationError::TypeError(TypeCheckError::UnsupportedTurbofishUsage { .. }),
CompilationError::TypeError(TypeCheckError::GenericCountMismatch { .. }),
));
}

#[test]
fn turbofish_in_constructor() {
let src = r#"
struct Foo<T> {
x: T
}
fn main() {
let x: Field = 0;
let _ = Foo::<i32> { x: x };
}
"#;

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

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

assert_eq!(expected_typ, "i32");
assert_eq!(expr_typ, "Field");
}

#[test]
fn turbofish_in_middle_of_variable_unsupported_yet() {
let src = r#"
Expand Down
2 changes: 1 addition & 1 deletion test_programs/execution_success/slice_regex/Nargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "regex"
name = "slice_regex"
type = "bin"
authors = [""]
compiler_version = ">=0.31.0"
Expand Down

0 comments on commit 106abd7

Please sign in to comment.