use super::ValidationError;
use crate::{ElicitCommunicator, ElicitResult, Elicitation, Prompt};
use anodized::spec;
#[cfg(not(kani))]
use elicitation_macros::instrumented_impl;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
macro_rules! impl_float_default_wrapper {
($primitive:ty, $wrapper:ident) => {
#[doc = concat!("Default wrapper for ", stringify!($primitive), " (unconstrained).")]
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
#[schemars(description = concat!(stringify!($primitive), " value"))]
pub struct $wrapper(
#[schemars(description = "Float value")]
$primitive
);
rmcp::elicit_safe!($wrapper);
impl $wrapper {
pub fn new(value: $primitive) -> Self {
Self(value)
}
pub fn get(&self) -> $primitive {
self.0
}
pub fn into_inner(self) -> $primitive {
self.0
}
}
paste::paste! {
crate::default_style!($wrapper => [<$wrapper Style>]);
impl Prompt for $wrapper {
fn prompt() -> Option<&'static str> {
Some("Please enter a number:")
}
}
impl Elicitation for $wrapper {
type Style = [<$wrapper Style>];
#[tracing::instrument(skip(communicator))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
let prompt = communicator
.style_context()
.prompt_for_type::<$primitive>(
"value",
stringify!($primitive),
&crate::style::PromptContext::new(0, 1),
)?
.unwrap_or_else(|| Self::prompt().unwrap().to_string());
tracing::debug!(
prompt = %prompt,
concat!("Eliciting ", stringify!($wrapper))
);
let response = communicator.send_prompt(&prompt).await?;
let value: $primitive = response.trim().parse().map_err(|e| {
crate::ElicitError::new(crate::ElicitErrorKind::ParseError(
format!("Failed to parse {}: {}", stringify!($primitive), e),
))
})?;
Ok(Self::new(value))
}
fn kani_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::kani_float_default(
stringify!($wrapper),
stringify!($primitive),
)
}
fn verus_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::verus_float_default(
stringify!($wrapper),
stringify!($primitive),
)
}
fn creusot_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::creusot_float_default(
stringify!($wrapper),
stringify!($primitive),
)
}
}
}
};
}
macro_rules! impl_float_serde_bridge {
($ty:ident, $prim:ty, $description:expr, { $($key:literal: $val:tt),* $(,)? }) => {
impl serde::Serialize for $ty {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
self.0.serialize(s)
}
}
impl<'de> serde::Deserialize<'de> for $ty {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let v = <$prim>::deserialize(d)?;
Self::new(v).map_err(serde::de::Error::custom)
}
}
impl schemars::JsonSchema for $ty {
fn schema_name() -> ::std::borrow::Cow<'static, str> {
stringify!($ty).into()
}
fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"type": "number",
"description": $description,
$($key: $val),*
})
}
}
};
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct F32Positive(f32);
#[cfg_attr(not(kani), instrumented_impl)]
impl F32Positive {
#[spec(requires: [value.is_finite(), value > 0.0])]
pub fn new(value: f32) -> Result<Self, ValidationError> {
#[cfg(kani)]
{
let is_finite: bool = kani::any();
let is_positive: bool = kani::any();
if !is_finite {
Err(ValidationError::NotFinite(String::new()))
} else if is_positive {
Ok(Self(value))
} else {
Err(ValidationError::FloatNotPositive(value as f64))
}
}
#[cfg(not(kani))]
{
if !value.is_finite() {
Err(ValidationError::NotFinite(format!("{}", value)))
} else if value > 0.0 {
Ok(Self(value))
} else {
Err(ValidationError::FloatNotPositive(value as f64))
}
}
}
pub fn get(&self) -> f32 {
self.0
}
pub fn into_inner(self) -> f32 {
self.0
}
}
crate::default_style!(F32Positive => F32PositiveStyle);
impl_float_serde_bridge!(F32Positive, f32, "Positive f32 value (> 0.0 and finite)", { "exclusiveMinimum": 0.0 });
impl Prompt for F32Positive {
fn prompt() -> Option<&'static str> {
Some("Please enter a positive number (> 0.0):")
}
}
impl Elicitation for F32Positive {
type Style = F32PositiveStyle;
#[tracing::instrument(skip(communicator), fields(type_name = "F32Positive"))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
tracing::debug!("Eliciting F32Positive (positive f32 value)");
loop {
let value = f32::elicit(communicator).await?;
match Self::new(value) {
Ok(positive) => {
tracing::debug!(value, "Valid F32Positive constructed");
return Ok(positive);
}
Err(e) => {
tracing::warn!(value, error = %e, "Invalid F32Positive, re-prompting");
}
}
}
}
fn kani_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::kani_float_positive("F32Positive", "f32")
}
fn verus_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::verus_float_default("F32Positive", "f32")
}
fn creusot_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::creusot_float_default("F32Positive", "f32")
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct F32NonNegative(f32);
#[cfg_attr(not(kani), instrumented_impl)]
impl F32NonNegative {
#[spec(requires: [value.is_finite(), value >= 0.0])]
pub fn new(value: f32) -> Result<Self, ValidationError> {
#[cfg(kani)]
{
let is_finite: bool = kani::any();
let is_non_negative: bool = kani::any();
if !is_finite {
Err(ValidationError::NotFinite(String::new()))
} else if is_non_negative {
Ok(Self(value))
} else {
Err(ValidationError::FloatNegative(value as f64))
}
}
#[cfg(not(kani))]
{
if !value.is_finite() {
Err(ValidationError::NotFinite(format!("{}", value)))
} else if value >= 0.0 {
Ok(Self(value))
} else {
Err(ValidationError::FloatNegative(value as f64))
}
}
}
pub fn get(&self) -> f32 {
self.0
}
pub fn into_inner(self) -> f32 {
self.0
}
}
crate::default_style!(F32NonNegative => F32NonNegativeStyle);
impl_float_serde_bridge!(F32NonNegative, f32, "Non-negative f32 value (>= 0.0 and finite)", { "minimum": 0.0 });
impl Prompt for F32NonNegative {
fn prompt() -> Option<&'static str> {
Some("Please enter a non-negative number (>= 0.0):")
}
}
impl Elicitation for F32NonNegative {
type Style = F32NonNegativeStyle;
#[tracing::instrument(skip(communicator), fields(type_name = "F32NonNegative"))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
tracing::debug!("Eliciting F32NonNegative (non-negative f32 value)");
loop {
let value = f32::elicit(communicator).await?;
match Self::new(value) {
Ok(non_negative) => {
tracing::debug!(value, "Valid F32NonNegative constructed");
return Ok(non_negative);
}
Err(e) => {
tracing::warn!(value, error = %e, "Invalid F32NonNegative, re-prompting");
}
}
}
}
fn kani_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::kani_float_nonneg("F32NonNegative", "f32")
}
fn verus_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::verus_float_default("F32NonNegative", "f32")
}
fn creusot_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::creusot_float_default("F32NonNegative", "f32")
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct F32Finite(f32);
#[cfg_attr(not(kani), instrumented_impl)]
impl F32Finite {
#[spec(requires: [value.is_finite()])]
pub fn new(value: f32) -> Result<Self, ValidationError> {
#[cfg(kani)]
{
let is_finite: bool = kani::any();
if is_finite {
Ok(Self(value))
} else {
Err(ValidationError::NotFinite(String::new()))
}
}
#[cfg(not(kani))]
{
if value.is_finite() {
Ok(Self(value))
} else {
Err(ValidationError::NotFinite(format!("{}", value)))
}
}
}
pub fn get(&self) -> f32 {
self.0
}
pub fn into_inner(self) -> f32 {
self.0
}
}
crate::default_style!(F32Finite => F32FiniteStyle);
impl_float_serde_bridge!(F32Finite, f32, "Finite f32 value (not NaN or infinite)", {});
impl Prompt for F32Finite {
fn prompt() -> Option<&'static str> {
Some("Please enter a finite number (not NaN or infinite):")
}
}
impl Elicitation for F32Finite {
type Style = F32FiniteStyle;
#[tracing::instrument(skip(communicator), fields(type_name = "F32Finite"))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
tracing::debug!("Eliciting F32Finite (finite f32 value)");
loop {
let value = f32::elicit(communicator).await?;
match Self::new(value) {
Ok(finite) => {
tracing::debug!(value, "Valid F32Finite constructed");
return Ok(finite);
}
Err(e) => {
tracing::warn!(value, error = %e, "Invalid F32Finite, re-prompting");
}
}
}
}
fn kani_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::kani_float_finite("F32Finite", "f32")
}
fn verus_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::verus_float_default("F32Finite", "f32")
}
fn creusot_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::creusot_float_default("F32Finite", "f32")
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct F64Positive(f64);
#[cfg_attr(not(kani), instrumented_impl)]
impl F64Positive {
#[spec(requires: [value.is_finite(), value > 0.0])]
pub fn new(value: f64) -> Result<Self, ValidationError> {
#[cfg(kani)]
{
let is_finite: bool = kani::any();
let is_positive: bool = kani::any();
if !is_finite {
Err(ValidationError::NotFinite(String::new()))
} else if is_positive {
Ok(Self(value))
} else {
Err(ValidationError::FloatNotPositive(value))
}
}
#[cfg(not(kani))]
{
if !value.is_finite() {
Err(ValidationError::NotFinite(format!("{}", value)))
} else if value > 0.0 {
Ok(Self(value))
} else {
Err(ValidationError::FloatNotPositive(value))
}
}
}
pub fn get(&self) -> f64 {
self.0
}
pub fn into_inner(self) -> f64 {
self.0
}
}
crate::default_style!(F64Positive => F64PositiveStyle);
impl_float_serde_bridge!(F64Positive, f64, "Positive f64 value (> 0.0 and finite)", { "exclusiveMinimum": 0.0 });
impl Prompt for F64Positive {
fn prompt() -> Option<&'static str> {
Some("Please enter a positive number (> 0.0):")
}
}
impl Elicitation for F64Positive {
type Style = F64PositiveStyle;
#[tracing::instrument(skip(communicator), fields(type_name = "F64Positive"))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
tracing::debug!("Eliciting F64Positive (positive f64 value)");
loop {
let value = f64::elicit(communicator).await?;
match Self::new(value) {
Ok(positive) => {
tracing::debug!(value, "Valid F64Positive constructed");
return Ok(positive);
}
Err(e) => {
tracing::warn!(value, error = %e, "Invalid F64Positive, re-prompting");
}
}
}
}
fn kani_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::kani_float_positive("F64Positive", "f64")
}
fn verus_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::verus_float_default("F64Positive", "f64")
}
fn creusot_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::creusot_float_default("F64Positive", "f64")
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct F64NonNegative(f64);
#[cfg_attr(not(kani), instrumented_impl)]
impl F64NonNegative {
#[spec(requires: [value.is_finite(), value >= 0.0])]
pub fn new(value: f64) -> Result<Self, ValidationError> {
#[cfg(kani)]
{
let is_finite: bool = kani::any();
let is_non_negative: bool = kani::any();
if !is_finite {
Err(ValidationError::NotFinite(String::new()))
} else if is_non_negative {
Ok(Self(value))
} else {
Err(ValidationError::FloatNegative(value))
}
}
#[cfg(not(kani))]
{
if !value.is_finite() {
Err(ValidationError::NotFinite(format!("{}", value)))
} else if value >= 0.0 {
Ok(Self(value))
} else {
Err(ValidationError::FloatNegative(value))
}
}
}
pub fn get(&self) -> f64 {
self.0
}
pub fn into_inner(self) -> f64 {
self.0
}
}
crate::default_style!(F64NonNegative => F64NonNegativeStyle);
impl_float_serde_bridge!(F64NonNegative, f64, "Non-negative f64 value (>= 0.0 and finite)", { "minimum": 0.0 });
impl Prompt for F64NonNegative {
fn prompt() -> Option<&'static str> {
Some("Please enter a non-negative number (>= 0.0):")
}
}
impl Elicitation for F64NonNegative {
type Style = F64NonNegativeStyle;
#[tracing::instrument(skip(communicator), fields(type_name = "F64NonNegative"))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
tracing::debug!("Eliciting F64NonNegative (non-negative f64 value)");
loop {
let value = f64::elicit(communicator).await?;
match Self::new(value) {
Ok(non_negative) => {
tracing::debug!(value, "Valid F64NonNegative constructed");
return Ok(non_negative);
}
Err(e) => {
tracing::warn!(value, error = %e, "Invalid F64NonNegative, re-prompting");
}
}
}
}
fn kani_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::kani_float_nonneg("F64NonNegative", "f64")
}
fn verus_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::verus_float_default("F64NonNegative", "f64")
}
fn creusot_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::creusot_float_default("F64NonNegative", "f64")
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct F64Finite(f64);
#[cfg_attr(not(kani), instrumented_impl)]
impl F64Finite {
#[spec(requires: [value.is_finite()])]
pub fn new(value: f64) -> Result<Self, ValidationError> {
if value.is_finite() {
Ok(Self(value))
} else {
Err(ValidationError::NotFinite(format!("{}", value)))
}
}
pub fn get(&self) -> f64 {
self.0
}
pub fn into_inner(self) -> f64 {
self.0
}
}
crate::default_style!(F64Finite => F64FiniteStyle);
impl_float_serde_bridge!(F64Finite, f64, "Finite f64 value (not NaN or infinite)", {});
impl Prompt for F64Finite {
fn prompt() -> Option<&'static str> {
Some("Please enter a finite number (not NaN or infinite):")
}
}
impl Elicitation for F64Finite {
type Style = F64FiniteStyle;
#[tracing::instrument(skip(communicator), fields(type_name = "F64Finite"))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
tracing::debug!("Eliciting F64Finite (finite f64 value)");
loop {
let value = f64::elicit(communicator).await?;
match Self::new(value) {
Ok(finite) => {
tracing::debug!(value, "Valid F64Finite constructed");
return Ok(finite);
}
Err(e) => {
tracing::warn!(value, error = %e, "Invalid F64Finite, re-prompting");
}
}
}
}
fn kani_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::kani_float_finite("F64Finite", "f64")
}
fn verus_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::verus_float_default("F64Finite", "f64")
}
fn creusot_proof() -> proc_macro2::TokenStream {
crate::verification::proof_helpers::creusot_float_default("F64Finite", "f64")
}
}
#[cfg(test)]
mod f32_positive_tests {
use super::*;
#[test]
fn f32_positive_new_valid() {
let result = F32Positive::new(1.5);
assert!(result.is_ok());
assert_eq!(result.unwrap().get(), 1.5);
}
#[test]
fn f32_positive_new_zero_invalid() {
let result = F32Positive::new(0.0);
assert!(result.is_err());
}
#[test]
fn f32_positive_new_negative_invalid() {
let result = F32Positive::new(-1.5);
assert!(result.is_err());
}
#[test]
fn f32_positive_new_nan_invalid() {
let result = F32Positive::new(f32::NAN);
assert!(result.is_err());
}
#[test]
fn f32_positive_new_infinity_invalid() {
let result = F32Positive::new(f32::INFINITY);
assert!(result.is_err());
}
#[test]
fn f32_positive_into_inner() {
let positive = F32Positive::new(42.5).unwrap();
let value: f32 = positive.into_inner();
assert_eq!(value, 42.5);
}
}
#[cfg(test)]
mod f32_nonnegative_tests {
use super::*;
#[test]
fn f32_nonnegative_new_valid_positive() {
let result = F32NonNegative::new(1.5);
assert!(result.is_ok());
assert_eq!(result.unwrap().get(), 1.5);
}
#[test]
fn f32_nonnegative_new_valid_zero() {
let result = F32NonNegative::new(0.0);
assert!(result.is_ok());
assert_eq!(result.unwrap().get(), 0.0);
}
#[test]
fn f32_nonnegative_new_negative_invalid() {
let result = F32NonNegative::new(-1.5);
assert!(result.is_err());
}
#[test]
fn f32_nonnegative_new_nan_invalid() {
let result = F32NonNegative::new(f32::NAN);
assert!(result.is_err());
}
#[test]
fn f32_nonnegative_into_inner() {
let non_neg = F32NonNegative::new(10.5).unwrap();
let value: f32 = non_neg.into_inner();
assert_eq!(value, 10.5);
}
}
#[cfg(test)]
mod f32_finite_tests {
use super::*;
#[test]
fn f32_finite_new_valid_positive() {
let result = F32Finite::new(1.5);
assert!(result.is_ok());
assert_eq!(result.unwrap().get(), 1.5);
}
#[test]
fn f32_finite_new_valid_negative() {
let result = F32Finite::new(-1.5);
assert!(result.is_ok());
assert_eq!(result.unwrap().get(), -1.5);
}
#[test]
fn f32_finite_new_valid_zero() {
let result = F32Finite::new(0.0);
assert!(result.is_ok());
assert_eq!(result.unwrap().get(), 0.0);
}
#[test]
fn f32_finite_new_nan_invalid() {
let result = F32Finite::new(f32::NAN);
assert!(result.is_err());
}
#[test]
fn f32_finite_new_infinity_invalid() {
let result = F32Finite::new(f32::INFINITY);
assert!(result.is_err());
}
#[test]
fn f32_finite_new_neg_infinity_invalid() {
let result = F32Finite::new(f32::NEG_INFINITY);
assert!(result.is_err());
}
#[test]
fn f32_finite_into_inner() {
let finite = F32Finite::new(42.5).unwrap();
let value: f32 = finite.into_inner();
assert_eq!(value, 42.5);
}
}
#[cfg(test)]
mod f64_positive_tests {
use super::*;
#[test]
fn f64_positive_new_valid() {
let result = F64Positive::new(1.5);
assert!(result.is_ok());
assert_eq!(result.unwrap().get(), 1.5);
}
#[test]
fn f64_positive_new_zero_invalid() {
let result = F64Positive::new(0.0);
assert!(result.is_err());
}
#[test]
fn f64_positive_new_negative_invalid() {
let result = F64Positive::new(-1.5);
assert!(result.is_err());
}
#[test]
fn f64_positive_new_nan_invalid() {
let result = F64Positive::new(f64::NAN);
assert!(result.is_err());
}
#[test]
fn f64_positive_new_infinity_invalid() {
let result = F64Positive::new(f64::INFINITY);
assert!(result.is_err());
}
#[test]
fn f64_positive_into_inner() {
let positive = F64Positive::new(42.5).unwrap();
let value: f64 = positive.into_inner();
assert_eq!(value, 42.5);
}
}
#[cfg(test)]
mod f64_nonnegative_tests {
use super::*;
#[test]
fn f64_nonnegative_new_valid_positive() {
let result = F64NonNegative::new(1.5);
assert!(result.is_ok());
assert_eq!(result.unwrap().get(), 1.5);
}
#[test]
fn f64_nonnegative_new_valid_zero() {
let result = F64NonNegative::new(0.0);
assert!(result.is_ok());
assert_eq!(result.unwrap().get(), 0.0);
}
#[test]
fn f64_nonnegative_new_negative_invalid() {
let result = F64NonNegative::new(-1.5);
assert!(result.is_err());
}
#[test]
fn f64_nonnegative_new_nan_invalid() {
let result = F64NonNegative::new(f64::NAN);
assert!(result.is_err());
}
#[test]
fn f64_nonnegative_into_inner() {
let non_neg = F64NonNegative::new(10.5).unwrap();
let value: f64 = non_neg.into_inner();
assert_eq!(value, 10.5);
}
}
#[cfg(test)]
mod f64_finite_tests {
use super::*;
#[test]
fn f64_finite_new_valid_positive() {
let result = F64Finite::new(1.5);
assert!(result.is_ok());
assert_eq!(result.unwrap().get(), 1.5);
}
#[test]
fn f64_finite_new_valid_negative() {
let result = F64Finite::new(-1.5);
assert!(result.is_ok());
assert_eq!(result.unwrap().get(), -1.5);
}
#[test]
fn f64_finite_new_valid_zero() {
let result = F64Finite::new(0.0);
assert!(result.is_ok());
assert_eq!(result.unwrap().get(), 0.0);
}
#[test]
fn f64_finite_new_nan_invalid() {
let result = F64Finite::new(f64::NAN);
assert!(result.is_err());
}
#[test]
fn f64_finite_new_infinity_invalid() {
let result = F64Finite::new(f64::INFINITY);
assert!(result.is_err());
}
#[test]
fn f64_finite_new_neg_infinity_invalid() {
let result = F64Finite::new(f64::NEG_INFINITY);
assert!(result.is_err());
}
#[test]
fn f64_finite_into_inner() {
let finite = F64Finite::new(42.5).unwrap();
let value: f64 = finite.into_inner();
assert_eq!(value, 42.5);
}
}
impl_float_default_wrapper!(f32, F32Default);
impl_float_default_wrapper!(f64, F64Default);
mod emit_impls {
use super::*;
use crate::emit_code::ToCodeLiteral;
use proc_macro2::TokenStream;
macro_rules! impl_to_code_literal_float {
($T:ident) => {
impl ToCodeLiteral for $T {
fn type_tokens() -> TokenStream {
concat!("elicitation::", stringify!($T))
.parse()
.expect("valid type path")
}
fn to_code_literal(&self) -> TokenStream {
let v = self.get();
let msg = concat!("valid ", stringify!($T));
let type_path: TokenStream = concat!("elicitation::", stringify!($T))
.parse()
.expect("valid type path");
quote::quote! { #type_path::new(#v).expect(#msg) }
}
}
};
}
impl_to_code_literal_float!(F32Positive);
impl_to_code_literal_float!(F32NonNegative);
impl_to_code_literal_float!(F32Finite);
impl_to_code_literal_float!(F64Positive);
impl_to_code_literal_float!(F64NonNegative);
impl_to_code_literal_float!(F64Finite);
}
macro_rules! impl_primitive_introspect {
($($ty:ty => $name:literal),+ $(,)?) => {
$(
impl crate::ElicitIntrospect for $ty {
fn pattern() -> crate::ElicitationPattern {
crate::ElicitationPattern::Primitive
}
fn metadata() -> crate::TypeMetadata {
crate::TypeMetadata {
type_name: $name,
description: <$ty as crate::Prompt>::prompt(),
details: crate::PatternDetails::Primitive,
}
}
}
)+
};
}
impl_primitive_introspect!(
F32Positive => "F32Positive",
F32NonNegative => "F32NonNegative",
F32Finite => "F32Finite",
F64Positive => "F64Positive",
F64NonNegative => "F64NonNegative",
F64Finite => "F64Finite",
);