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: Add std::meta::typ::fresh_type_variable #5948

Merged
merged 5 commits into from
Sep 6, 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
23 changes: 14 additions & 9 deletions compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ impl<'local, 'context> Interpreter<'local, 'context> {
"expr_resolve" => expr_resolve(self, arguments, location),
"is_unconstrained" => Ok(Value::Bool(true)),
"fmtstr_quoted_contents" => fmtstr_quoted_contents(interner, arguments, location),
"fresh_type_variable" => fresh_type_variable(interner),
"function_def_add_attribute" => function_def_add_attribute(self, arguments, location),
"function_def_body" => function_def_body(interner, arguments, location),
"function_def_has_named_attribute" => {
Expand Down Expand Up @@ -614,11 +615,10 @@ fn type_as_constant(
location: Location,
) -> IResult<Value> {
type_as(arguments, return_type, location, |typ| {
if let Type::Constant(n) = typ {
Some(Value::U32(n))
} else {
None
}
// Prefer to use `evaluate_to_u32` over matching on `Type::Constant`
// since arithmetic generics may be `Type::InfixExpr`s which evaluate to
// constants but are not actually the `Type::Constant` variant.
typ.evaluate_to_u32().map(Value::U32)
})
}

Expand Down Expand Up @@ -720,7 +720,7 @@ where
F: FnOnce(Type) -> Option<Value>,
{
let value = check_one_argument(arguments, location)?;
let typ = get_type(value)?;
let typ = get_type(value)?.follow_bindings();

let option_value = f(typ);

Expand Down Expand Up @@ -749,13 +749,13 @@ fn type_get_trait_impl(
let typ = get_type(typ)?;
let (trait_id, generics) = get_trait_constraint(constraint)?;

let option_value = match interner.try_lookup_trait_implementation(
let option_value = match interner.lookup_trait_implementation(
&typ,
trait_id,
&generics.ordered,
&generics.named,
) {
Ok((TraitImplKind::Normal(trait_impl_id), _)) => Some(Value::TraitImpl(trait_impl_id)),
Ok(TraitImplKind::Normal(trait_impl_id)) => Some(Value::TraitImpl(trait_impl_id)),
_ => None,
};

Expand All @@ -774,7 +774,7 @@ fn type_implements(
let (trait_id, generics) = get_trait_constraint(constraint)?;

let implements = interner
.try_lookup_trait_implementation(&typ, trait_id, &generics.ordered, &generics.named)
.lookup_trait_implementation(&typ, trait_id, &generics.ordered, &generics.named)
.is_ok();
Ok(Value::Bool(implements))
}
Expand Down Expand Up @@ -1670,6 +1670,11 @@ fn fmtstr_quoted_contents(
Ok(Value::Quoted(Rc::new(tokens)))
}

// fn fresh_type_variable() -> Type
fn fresh_type_variable(interner: &NodeInterner) -> IResult<Value> {
Ok(Value::Type(interner.next_type_variable()))
}

// fn add_attribute<let N: u32>(self, attribute: str<N>)
fn function_def_add_attribute(
interpreter: &mut Interpreter,
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/hir/comptime/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@
Value::Expr(ExprValue::Statement(statement))
}

pub(crate) fn lvalue(lvaue: LValue) -> Self {

Check warning on line 98 in compiler/noirc_frontend/src/hir/comptime/value.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (lvaue)
Value::Expr(ExprValue::LValue(lvaue))

Check warning on line 99 in compiler/noirc_frontend/src/hir/comptime/value.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (lvaue)
}

pub(crate) fn get_type(&self) -> Cow<Type> {
Expand Down Expand Up @@ -655,7 +655,7 @@
}
Value::ModuleDefinition(_) => write!(f, "(module)"),
Value::Zeroed(typ) => write!(f, "(zeroed {typ})"),
Value::Type(typ) => write!(f, "{}", typ),
Value::Type(typ) => write!(f, "{:?}", typ),
Value::Expr(ExprValue::Expression(expr)) => {
write!(f, "{}", remove_interned_in_expression_kind(self.interner, expr.clone()))
}
Expand Down
24 changes: 24 additions & 0 deletions docs/docs/noir/standard_library/meta/typ.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,30 @@ title: Type
`std::meta::typ` contains methods on the built-in `Type` type used for representing
a type in the source program.

## Functions

#include_code fresh_type_variable noir_stdlib/src/meta/typ.nr rust

Creates and returns an unbound type variable. This is a special kind of type internal
to type checking which will type check with any other type. When it is type checked
against another type it will also be set to that type. For example, if `a` is a type
variable and we have the type equality `(a, i32) = (u8, i32)`, the compiler will set
`a` equal to `u8`.

Unbound type variables will often be rendered as `_` while printing them. Bound type
variables will appear as the type they are bound to.

This can be used in conjunction with functions which internally perform type checks
such as `Type::implements` or `Type::get_trait_impl` to potentially grab some of the types used.

Note that calling `Type::implements` or `Type::get_trait_impl` on a type variable will always
fail.

Example:

#include_code serialize-setup test_programs/compile_success_empty/comptime_type/src/main.nr rust
#include_code fresh-type-variable-example test_programs/compile_success_empty/comptime_type/src/main.nr rust

## Methods

### as_array
Expand Down
5 changes: 5 additions & 0 deletions noir_stdlib/src/meta/typ.nr
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use crate::cmp::Eq;
use crate::option::Option;

#[builtin(fresh_type_variable)]
// docs:start:fresh_type_variable
pub fn fresh_type_variable() -> Type {}
// docs:end:fresh_type_variable

impl Type {
#[builtin(type_as_array)]
// docs:start:as_array
Expand Down
35 changes: 35 additions & 0 deletions test_programs/compile_success_empty/comptime_type/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ struct StructDoesNotImplementSomeTrait {

}

// docs:start:serialize-setup
trait Serialize<let N: u32> {}

impl Serialize<1> for Field {}

impl<T, let N: u32, let M: u32> Serialize<N * M> for [T; N]
where T: Serialize<M> {}

impl<T, U, let N: u32, let M: u32> Serialize<N + M> for (T, U)
where T: Serialize<N>, U: Serialize<M> {}
// docs:end:serialize-setup

fn main() {
comptime
{
Expand Down Expand Up @@ -115,6 +127,29 @@ fn main() {
let str_type = quote { str<10> }.as_type();
let constant = str_type.as_str().unwrap();
assert_eq(constant.as_constant().unwrap(), 10);

// Check std::meta::typ::fresh_type_variable
// docs:start:fresh-type-variable-example
let typevar1 = std::meta::typ::fresh_type_variable();
let constraint = quote { Serialize<$typevar1> }.as_trait_constraint();
let field_type = quote { Field }.as_type();

// Search for a trait impl (binding typevar1 to 1 when the impl is found):
assert(field_type.implements(constraint));

// typevar1 should be bound to the "1" generic now:
assert_eq(typevar1.as_constant().unwrap(), 1);

// If we want to do the same with a different type, we need to
// create a new type variable now that `typevar1` is bound
let typevar2 = std::meta::typ::fresh_type_variable();
let constraint = quote { Serialize<$typevar2> }.as_trait_constraint();
let array_type = quote { [(Field, Field); 5] }.as_type();
assert(array_type.implements(constraint));

// Now typevar2 should be bound to the serialized pair size 2 times the array length 5
assert_eq(typevar2.as_constant().unwrap(), 10);
Thunkar marked this conversation as resolved.
Show resolved Hide resolved
// docs:end:fresh-type-variable-example
}
}

Expand Down
Loading