Skip to content

Commit 3dab4dd

Browse files
jfecherTomAFrench
andauthored
feat: Add std::meta::typ::fresh_type_variable (#5948)
# Description ## Problem\* Resolves #5873 ## Summary\* Adds a metaprogramming function to create new type variables. Vaguely useful sometimes for impl searches to get the value of other types used in the impl search. ## Additional Context ## Documentation\* Check one: - [ ] No documentation needed. - [x] 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. --------- Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com>
1 parent 83e1d26 commit 3dab4dd

File tree

5 files changed

+79
-10
lines changed
  • compiler/noirc_frontend/src/hir/comptime
  • docs/docs/noir/standard_library/meta
  • noir_stdlib/src/meta
  • test_programs/compile_success_empty/comptime_type/src

5 files changed

+79
-10
lines changed

compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ impl<'local, 'context> Interpreter<'local, 'context> {
9696
"expr_resolve" => expr_resolve(self, arguments, location),
9797
"is_unconstrained" => Ok(Value::Bool(true)),
9898
"fmtstr_quoted_contents" => fmtstr_quoted_contents(interner, arguments, location),
99+
"fresh_type_variable" => fresh_type_variable(interner),
99100
"function_def_add_attribute" => function_def_add_attribute(self, arguments, location),
100101
"function_def_body" => function_def_body(interner, arguments, location),
101102
"function_def_has_named_attribute" => {
@@ -680,11 +681,10 @@ fn type_as_constant(
680681
location: Location,
681682
) -> IResult<Value> {
682683
type_as(arguments, return_type, location, |typ| {
683-
if let Type::Constant(n) = typ {
684-
Some(Value::U32(n))
685-
} else {
686-
None
687-
}
684+
// Prefer to use `evaluate_to_u32` over matching on `Type::Constant`
685+
// since arithmetic generics may be `Type::InfixExpr`s which evaluate to
686+
// constants but are not actually the `Type::Constant` variant.
687+
typ.evaluate_to_u32().map(Value::U32)
688688
})
689689
}
690690

@@ -786,7 +786,7 @@ where
786786
F: FnOnce(Type) -> Option<Value>,
787787
{
788788
let value = check_one_argument(arguments, location)?;
789-
let typ = get_type(value)?;
789+
let typ = get_type(value)?.follow_bindings();
790790

791791
let option_value = f(typ);
792792

@@ -815,13 +815,13 @@ fn type_get_trait_impl(
815815
let typ = get_type(typ)?;
816816
let (trait_id, generics) = get_trait_constraint(constraint)?;
817817

818-
let option_value = match interner.try_lookup_trait_implementation(
818+
let option_value = match interner.lookup_trait_implementation(
819819
&typ,
820820
trait_id,
821821
&generics.ordered,
822822
&generics.named,
823823
) {
824-
Ok((TraitImplKind::Normal(trait_impl_id), _)) => Some(Value::TraitImpl(trait_impl_id)),
824+
Ok(TraitImplKind::Normal(trait_impl_id)) => Some(Value::TraitImpl(trait_impl_id)),
825825
_ => None,
826826
};
827827

@@ -840,7 +840,7 @@ fn type_implements(
840840
let (trait_id, generics) = get_trait_constraint(constraint)?;
841841

842842
let implements = interner
843-
.try_lookup_trait_implementation(&typ, trait_id, &generics.ordered, &generics.named)
843+
.lookup_trait_implementation(&typ, trait_id, &generics.ordered, &generics.named)
844844
.is_ok();
845845
Ok(Value::Bool(implements))
846846
}
@@ -1736,6 +1736,11 @@ fn fmtstr_quoted_contents(
17361736
Ok(Value::Quoted(Rc::new(tokens)))
17371737
}
17381738

1739+
// fn fresh_type_variable() -> Type
1740+
fn fresh_type_variable(interner: &NodeInterner) -> IResult<Value> {
1741+
Ok(Value::Type(interner.next_type_variable()))
1742+
}
1743+
17391744
// fn add_attribute<let N: u32>(self, attribute: str<N>)
17401745
fn function_def_add_attribute(
17411746
interpreter: &mut Interpreter,

compiler/noirc_frontend/src/hir/comptime/value.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,7 @@ impl<'value, 'interner> Display for ValuePrinter<'value, 'interner> {
655655
}
656656
Value::ModuleDefinition(_) => write!(f, "(module)"),
657657
Value::Zeroed(typ) => write!(f, "(zeroed {typ})"),
658-
Value::Type(typ) => write!(f, "{}", typ),
658+
Value::Type(typ) => write!(f, "{:?}", typ),
659659
Value::Expr(ExprValue::Expression(expr)) => {
660660
write!(f, "{}", remove_interned_in_expression_kind(self.interner, expr.clone()))
661661
}

docs/docs/noir/standard_library/meta/typ.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,30 @@ title: Type
55
`std::meta::typ` contains methods on the built-in `Type` type used for representing
66
a type in the source program.
77

8+
## Functions
9+
10+
#include_code fresh_type_variable noir_stdlib/src/meta/typ.nr rust
11+
12+
Creates and returns an unbound type variable. This is a special kind of type internal
13+
to type checking which will type check with any other type. When it is type checked
14+
against another type it will also be set to that type. For example, if `a` is a type
15+
variable and we have the type equality `(a, i32) = (u8, i32)`, the compiler will set
16+
`a` equal to `u8`.
17+
18+
Unbound type variables will often be rendered as `_` while printing them. Bound type
19+
variables will appear as the type they are bound to.
20+
21+
This can be used in conjunction with functions which internally perform type checks
22+
such as `Type::implements` or `Type::get_trait_impl` to potentially grab some of the types used.
23+
24+
Note that calling `Type::implements` or `Type::get_trait_impl` on a type variable will always
25+
fail.
26+
27+
Example:
28+
29+
#include_code serialize-setup test_programs/compile_success_empty/comptime_type/src/main.nr rust
30+
#include_code fresh-type-variable-example test_programs/compile_success_empty/comptime_type/src/main.nr rust
31+
832
## Methods
933

1034
### as_array

noir_stdlib/src/meta/typ.nr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
use crate::cmp::Eq;
22
use crate::option::Option;
33

4+
#[builtin(fresh_type_variable)]
5+
// docs:start:fresh_type_variable
6+
pub fn fresh_type_variable() -> Type {}
7+
// docs:end:fresh_type_variable
8+
49
impl Type {
510
#[builtin(type_as_array)]
611
// docs:start:as_array

test_programs/compile_success_empty/comptime_type/src/main.nr

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ struct StructDoesNotImplementSomeTrait {
1919

2020
}
2121

22+
// docs:start:serialize-setup
23+
trait Serialize<let N: u32> {}
24+
25+
impl Serialize<1> for Field {}
26+
27+
impl<T, let N: u32, let M: u32> Serialize<N * M> for [T; N]
28+
where T: Serialize<M> {}
29+
30+
impl<T, U, let N: u32, let M: u32> Serialize<N + M> for (T, U)
31+
where T: Serialize<N>, U: Serialize<M> {}
32+
// docs:end:serialize-setup
33+
2234
fn main() {
2335
comptime
2436
{
@@ -115,6 +127,29 @@ fn main() {
115127
let str_type = quote { str<10> }.as_type();
116128
let constant = str_type.as_str().unwrap();
117129
assert_eq(constant.as_constant().unwrap(), 10);
130+
131+
// Check std::meta::typ::fresh_type_variable
132+
// docs:start:fresh-type-variable-example
133+
let typevar1 = std::meta::typ::fresh_type_variable();
134+
let constraint = quote { Serialize<$typevar1> }.as_trait_constraint();
135+
let field_type = quote { Field }.as_type();
136+
137+
// Search for a trait impl (binding typevar1 to 1 when the impl is found):
138+
assert(field_type.implements(constraint));
139+
140+
// typevar1 should be bound to the "1" generic now:
141+
assert_eq(typevar1.as_constant().unwrap(), 1);
142+
143+
// If we want to do the same with a different type, we need to
144+
// create a new type variable now that `typevar1` is bound
145+
let typevar2 = std::meta::typ::fresh_type_variable();
146+
let constraint = quote { Serialize<$typevar2> }.as_trait_constraint();
147+
let array_type = quote { [(Field, Field); 5] }.as_type();
148+
assert(array_type.implements(constraint));
149+
150+
// Now typevar2 should be bound to the serialized pair size 2 times the array length 5
151+
assert_eq(typevar2.as_constant().unwrap(), 10);
152+
// docs:end:fresh-type-variable-example
118153
}
119154
}
120155

0 commit comments

Comments
 (0)