diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 0b028217c46..82f6c002d0c 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -193,6 +193,9 @@ impl<'local, 'context> Interpreter<'local, 'context> { "type_as_array" => type_as_array(arguments, return_type, location), "type_as_constant" => type_as_constant(arguments, return_type, location), "type_as_integer" => type_as_integer(arguments, return_type, location), + "type_as_mutable_reference" => { + type_as_mutable_reference(arguments, return_type, location) + } "type_as_slice" => type_as_slice(arguments, return_type, location), "type_as_str" => type_as_str(arguments, return_type, location), "type_as_struct" => type_as_struct(arguments, return_type, location), @@ -205,6 +208,7 @@ impl<'local, 'context> Interpreter<'local, 'context> { "type_implements" => type_implements(interner, arguments, location), "type_is_bool" => type_is_bool(arguments, location), "type_is_field" => type_is_field(arguments, location), + "type_is_unit" => type_is_unit(arguments, location), "type_of" => type_of(arguments, location), "typed_expr_as_function_definition" => { typed_expr_as_function_definition(interner, arguments, return_type, location) @@ -212,7 +216,15 @@ impl<'local, 'context> Interpreter<'local, 'context> { "typed_expr_get_type" => { typed_expr_get_type(interner, arguments, return_type, location) } + "unresolved_type_as_mutable_reference" => { + unresolved_type_as_mutable_reference(interner, arguments, return_type, location) + } + "unresolved_type_as_slice" => { + unresolved_type_as_slice(interner, arguments, return_type, location) + } + "unresolved_type_is_bool" => unresolved_type_is_bool(interner, arguments, location), "unresolved_type_is_field" => unresolved_type_is_field(interner, arguments, location), + "unresolved_type_is_unit" => unresolved_type_is_unit(interner, arguments, location), "zeroed" => zeroed(return_type), _ => { let item = format!("Comptime evaluation for builtin function {name}"); @@ -855,6 +867,21 @@ fn type_as_integer( }) } +// fn as_mutable_reference(self) -> Option +fn type_as_mutable_reference( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + type_as(arguments, return_type, location, |typ| { + if let Type::MutableReference(typ) = typ { + Some(Value::Type(*typ)) + } else { + None + } + }) +} + // fn as_slice(self) -> Option fn type_as_slice( arguments: Vec<(Value, Location)>, @@ -1013,6 +1040,14 @@ fn type_is_field(arguments: Vec<(Value, Location)>, location: Location) -> IResu Ok(Value::Bool(matches!(typ, Type::FieldElement))) } +// fn is_unit(self) -> bool +fn type_is_unit(arguments: Vec<(Value, Location)>, location: Location) -> IResult { + let value = check_one_argument(arguments, location)?; + let typ = get_type(value)?; + + Ok(Value::Bool(matches!(typ, Type::Unit))) +} + // fn type_of(x: T) -> Type fn type_of(arguments: Vec<(Value, Location)>, location: Location) -> IResult { let (value, _) = check_one_argument(arguments, location)?; @@ -1115,6 +1150,49 @@ fn typed_expr_get_type( option(return_type, option_value) } +// fn as_mutable_reference(self) -> Option +fn unresolved_type_as_mutable_reference( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + unresolved_type_as(interner, arguments, return_type, location, |typ| { + if let UnresolvedTypeData::MutableReference(typ) = typ { + Some(Value::UnresolvedType(typ.typ)) + } else { + None + } + }) +} + +// fn as_slice(self) -> Option +fn unresolved_type_as_slice( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + unresolved_type_as(interner, arguments, return_type, location, |typ| { + if let UnresolvedTypeData::Slice(typ) = typ { + Some(Value::UnresolvedType(typ.typ)) + } else { + None + } + }) +} + +// fn is_bool(self) -> bool +fn unresolved_type_is_bool( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let typ = get_unresolved_type(interner, self_argument)?; + Ok(Value::Bool(matches!(typ, UnresolvedTypeData::Bool))) +} + // fn is_field(self) -> bool fn unresolved_type_is_field( interner: &NodeInterner, @@ -1126,6 +1204,36 @@ fn unresolved_type_is_field( Ok(Value::Bool(matches!(typ, UnresolvedTypeData::FieldElement))) } +// fn is_unit(self) -> bool +fn unresolved_type_is_unit( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let typ = get_unresolved_type(interner, self_argument)?; + Ok(Value::Bool(matches!(typ, UnresolvedTypeData::Unit))) +} + +// Helper function for implementing the `unresolved_type_as_...` functions. +fn unresolved_type_as( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, + f: F, +) -> IResult +where + F: FnOnce(UnresolvedTypeData) -> Option, +{ + let value = check_one_argument(arguments, location)?; + let typ = get_unresolved_type(interner, value)?; + + let option_value = f(typ); + + option(return_type, option_value) +} + // fn zeroed() -> T fn zeroed(return_type: Type) -> IResult { match return_type { diff --git a/docs/docs/noir/standard_library/meta/typ.md b/docs/docs/noir/standard_library/meta/typ.md index 5a8b43b1dfa..71a36e629c6 100644 --- a/docs/docs/noir/standard_library/meta/typ.md +++ b/docs/docs/noir/standard_library/meta/typ.md @@ -63,6 +63,12 @@ return the numeric constant. If this is an integer type, return a boolean which is `true` if the type is signed, as well as the number of bits of this integer type. +### as_mutable_reference + +#include_code as_mutable_reference noir_stdlib/src/meta/typ.nr rust + +If this is a mutable reference type `&mut T`, returns the mutable type `T`. + ### as_slice #include_code as_slice noir_stdlib/src/meta/typ.nr rust @@ -146,6 +152,12 @@ fn foo() where T: Default { `true` if this type is `Field`. +### is_unit + +#include_code is_unit noir_stdlib/src/meta/typ.nr rust + +`true` if this type is the unit `()` type. + ## Trait Implementations ```rust diff --git a/docs/docs/noir/standard_library/meta/unresolved_type.md b/docs/docs/noir/standard_library/meta/unresolved_type.md index 9c61f91dee2..8535cbab19c 100644 --- a/docs/docs/noir/standard_library/meta/unresolved_type.md +++ b/docs/docs/noir/standard_library/meta/unresolved_type.md @@ -6,8 +6,32 @@ title: UnresolvedType ## Methods +### as_mutable_reference + +#include_code as_mutable_reference noir_stdlib/src/meta/unresolved_type.nr rust + +If this is a mutable reference type `&mut T`, returns the mutable type `T`. + +### as_slice + +#include_code as_slice noir_stdlib/src/meta/unresolved_type.nr rust + +If this is a slice `&[T]`, returns the element type `T`. + +### is_bool + +#include_code is_bool noir_stdlib/src/meta/unresolved_type.nr rust + +Returns `true` if this type is `bool`. + ### is_field #include_code is_field noir_stdlib/src/meta/unresolved_type.nr rust Returns true if this type refers to the Field type. + +### is_unit + +#include_code is_unit noir_stdlib/src/meta/unresolved_type.nr rust + +Returns true if this type is the unit `()` type. diff --git a/noir_stdlib/src/meta/typ.nr b/noir_stdlib/src/meta/typ.nr index 50f8fa60fb9..31fdbb49b53 100644 --- a/noir_stdlib/src/meta/typ.nr +++ b/noir_stdlib/src/meta/typ.nr @@ -1,69 +1,195 @@ +//! Contains methods on the built-in `Type` type used for representing a type in the source program. + use crate::cmp::Eq; use crate::option::Option; +/// 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: +/// +/// ```noir +/// trait Serialize {} +/// +/// impl Serialize<1> for Field {} +/// +/// impl Serialize for [T; N] +/// where T: Serialize {} +/// +/// impl Serialize for (T, U) +/// where T: Serialize, U: Serialize {} +/// +/// fn fresh_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); +/// } +/// ``` #[builtin(fresh_type_variable)] // docs:start:fresh_type_variable pub comptime fn fresh_type_variable() -> Type {} // docs:end:fresh_type_variable impl Type { + /// If this type is an array, return a pair of (element type, size type). + /// + /// Example: + /// + /// ```noir + /// comptime { + /// let array_type = quote { [Field; 3] }.as_type(); + /// let (field_type, three_type) = array_type.as_array().unwrap(); + /// + /// assert(field_type.is_field()); + /// assert_eq(three_type.as_constant().unwrap(), 3); + /// } + /// ``` #[builtin(type_as_array)] // docs:start:as_array pub comptime fn as_array(self) -> Option<(Type, Type)> {} // docs:end:as_array + /// If this type is a constant integer (such as the `3` in the array type `[Field; 3]`), + /// return the numeric constant. #[builtin(type_as_constant)] // docs:start:as_constant pub comptime fn as_constant(self) -> Option {} // docs:end:as_constant + /// If this is an integer type, return a boolean which is `true` + /// if the type is signed, as well as the number of bits of this integer type. #[builtin(type_as_integer)] // docs:start:as_integer pub comptime fn as_integer(self) -> Option<(bool, u8)> {} // docs:end:as_integer + /// If this is a mutable reference type `&mut T`, returns the mutable type `T`. + #[builtin(type_as_mutable_reference)] + // docs:start:as_mutable_reference + comptime fn as_mutable_reference(self) -> Option {} + // docs:end:as_mutable_reference + + /// If this is a slice type, return the element type of the slice. #[builtin(type_as_slice)] // docs:start:as_slice pub comptime fn as_slice(self) -> Option {} // docs:end:as_slice + /// If this is a `str` type, returns the length `N` as a type. #[builtin(type_as_str)] // docs:start:as_str pub comptime fn as_str(self) -> Option {} // docs:end:as_str + /// If this is a struct type, returns the struct in addition to any generic arguments on this type. #[builtin(type_as_struct)] // docs:start:as_struct pub comptime fn as_struct(self) -> Option<(StructDefinition, [Type])> {} // docs:end:as_struct + /// If this is a tuple type, returns each element type of the tuple. #[builtin(type_as_tuple)] // docs:start:as_tuple pub comptime fn as_tuple(self) -> Option<[Type]> {} // docs:end:as_tuple + /// Retrieves the trait implementation that implements the given + /// trait constraint for this type. If the trait constraint is not + /// found, `None` is returned. Note that since the concrete trait implementation + /// for a trait constraint specified from a `where` clause is unknown, + /// this function will return `None` in these cases. If you only want to know + /// whether a type implements a trait, use `implements` instead. + /// + /// Example: + /// + /// ```rust + /// comptime { + /// let field_type = quote { Field }.as_type(); + /// let default = quote { Default }.as_trait_constraint(); + /// + /// let the_impl: TraitImpl = field_type.get_trait_impl(default).unwrap(); + /// assert(the_impl.methods().len(), 1); + /// } + /// ``` #[builtin(type_get_trait_impl)] // docs:start:get_trait_impl pub comptime fn get_trait_impl(self, constraint: TraitConstraint) -> Option {} // docs:end:get_trait_impl + /// Returns `true` if this type implements the given trait. Note that unlike + /// `get_trait_impl` this will also return true for any `where` constraints + /// in scope. + /// + /// Example: + /// + /// ```rust + /// fn foo() where T: Default { + /// comptime { + /// let field_type = quote { Field }.as_type(); + /// let default = quote { Default }.as_trait_constraint(); + /// assert(field_type.implements(default)); + /// + /// let t = quote { T }.as_type(); + /// assert(t.implements(default)); + /// } + /// } + /// ``` #[builtin(type_implements)] // docs:start:implements pub comptime fn implements(self, constraint: TraitConstraint) -> bool {} // docs:end:implements + /// Returns `true` if this type is `bool`. #[builtin(type_is_bool)] // docs:start:is_bool pub comptime fn is_bool(self) -> bool {} // docs:end:is_bool + /// Returns `true` if this type is `Field`. #[builtin(type_is_field)] -// docs:start:is_field + // docs:start:is_field pub comptime fn is_field(self) -> bool {} // docs:end:is_field + + /// Returns `true` if this type is the unit `()` type. + #[builtin(type_is_unit)] + // docs:start:is_unit + comptime fn is_unit(self) -> bool {} + // docs:end:is_unit } impl Eq for Type { + /// Note that this is syntactic equality, this is not the same as whether two types will type check + /// to be the same type. Unless type inference or generics are being used however, users should not + /// typically have to worry about this distinction. comptime fn eq(self, other: Self) -> bool { type_eq(self, other) } diff --git a/noir_stdlib/src/meta/unresolved_type.nr b/noir_stdlib/src/meta/unresolved_type.nr index c6e6d396546..5c2e8803674 100644 --- a/noir_stdlib/src/meta/unresolved_type.nr +++ b/noir_stdlib/src/meta/unresolved_type.nr @@ -1,6 +1,35 @@ +//! Contains methods on the built-in `UnresolvedType` type for the syntax of types. + +use crate::option::Option; + impl UnresolvedType { + /// If this is a mutable reference type `&mut T`, returns the mutable type `T`. + #[builtin(unresolved_type_as_mutable_reference)] + // docs:start:as_mutable_reference + comptime fn as_mutable_reference(self) -> Option {} + // docs:end:as_mutable_reference + + /// If this is a slice `&[T]`, returns the element type `T`. + #[builtin(unresolved_type_as_slice)] + // docs:start:as_slice + comptime fn as_slice(self) -> Option {} + // docs:end:as_slice + + /// Returns `true` if this type is `bool`. + #[builtin(unresolved_type_is_bool)] + // docs:start:is_bool + comptime fn is_bool(self) -> bool {} + // docs:end:is_bool + + /// Returns true if this type refers to the `Field` type. #[builtin(unresolved_type_is_field)] // docs:start:is_field pub comptime fn is_field(self) -> bool {} // docs:end:is_field + + /// Returns true if this type is the unit `()` type. + #[builtin(unresolved_type_is_unit)] + // docs:start:is_unit + comptime fn is_unit(self) -> bool {} + // docs:end:is_unit } diff --git a/test_programs/compile_success_empty/comptime_type/src/main.nr b/test_programs/compile_success_empty/comptime_type/src/main.nr index c7b3d0b9400..2b1bd215960 100644 --- a/test_programs/compile_success_empty/comptime_type/src/main.nr +++ b/test_programs/compile_success_empty/comptime_type/src/main.nr @@ -148,6 +148,14 @@ fn main() { // Now typevar2 should be bound to the serialized pair size 2 times the array length 5 assert_eq(typevar2.as_constant().unwrap(), 10); // docs:end:fresh-type-variable-example + + // Check Type::is_unit + let unit = quote { () }.as_type(); + assert(unit.is_unit()); + + // Check Type::as_mutable_reference + let typ = quote { &mut Field }.as_type(); + assert_eq(typ.as_mutable_reference().unwrap(), quote { Field }.as_type()); } } diff --git a/test_programs/compile_success_empty/comptime_unresolved_type/Nargo.toml b/test_programs/compile_success_empty/comptime_unresolved_type/Nargo.toml new file mode 100644 index 00000000000..cc266b9b9c5 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_unresolved_type/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_unresolved_type" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_success_empty/comptime_unresolved_type/src/main.nr b/test_programs/compile_success_empty/comptime_unresolved_type/src/main.nr new file mode 100644 index 00000000000..aba4461d4af --- /dev/null +++ b/test_programs/compile_success_empty/comptime_unresolved_type/src/main.nr @@ -0,0 +1,24 @@ +fn main() { + comptime + { + // Check UnresolvedType::is_bool + let typ = quote { x as bool }.as_expr().unwrap().as_cast().unwrap().1; + assert(typ.is_bool()); + + // Check UnresolvedType::is_field + let typ = quote { x as Field }.as_expr().unwrap().as_cast().unwrap().1; + assert(typ.is_field()); + + // Check UnresolvedType::is_unit + let typ = quote { x as () }.as_expr().unwrap().as_cast().unwrap().1; + assert(typ.is_unit()); + + // Check UnresolvedType::as_mutable_reference + let typ = quote { x as &mut Field }.as_expr().unwrap().as_cast().unwrap().1; + assert(typ.as_mutable_reference().unwrap().is_field()); + + // Check UnresolvedType::as_slice + let typ = quote { x as [Field] }.as_expr().unwrap().as_cast().unwrap().1; + assert(typ.as_slice().unwrap().is_field()); + } +}