getargs-derive-macros 0.1.0

Internal implementation detail of getargs-derive. Not meant for direct use; depend on getargs-derive instead.
Documentation
use super::{DefaultSource, DefaultValue, FieldKind, FieldModel, RawField};
use crate::model::attr::{require_value, set_once};
use crate::model::errors::Errors;
use crate::model::types::{collection_element, option_inner_type};
use proc_macro2::Span;
use syn::{
    Expr, ExprLit, ExprRange, Lit, LitStr, RangeLimits, Type, meta::ParseNestedMeta,
    spanned::Spanned,
};

const SUPPORTED: &str = "`getargs_derive::Values<T, N>`, `heapless::Vec<T, N>` (feature `heapless`) or `Vec<T>` (feature `alloc`)";

#[derive(Default)]
pub(super) struct RawCollection {
    default_values: Option<(Vec<LitStr>, Span)>,
    num_args: Option<((usize, Option<usize>), Span)>,
    value_delimiter: Option<(char, Span)>,
}

pub(crate) struct CollectionModel {
    pub(crate) delimiter: Option<char>,
    pub(crate) min: usize,
    pub(crate) max: Option<usize>,
    pub(crate) default_values: Vec<LitStr>,
}

impl FieldModel {
    pub(crate) fn ellipsis(&self) -> &'static str {
        if self.collection.is_some() { "..." } else { "" }
    }
}

pub(super) fn default_values(meta: &ParseNestedMeta, raw: &mut RawField) -> syn::Result<()> {
    let example = "default_values = [\"a\", \"b\"]";
    let expr: Expr = require_value(meta, "default_values", example)?.parse()?;
    let Expr::Array(array) = expr else {
        return Err(syn::Error::new_spanned(
            expr,
            format!("`default_values` expects an array of string literals, e.g. `{example}`"),
        ));
    };
    if array.elems.is_empty() {
        return Err(syn::Error::new_spanned(
            array,
            "`default_values` cannot be empty; a collection is already empty when absent",
        ));
    }
    let mut values = Vec::new();
    for element in &array.elems {
        match element {
            Expr::Lit(ExprLit {
                lit: Lit::Str(literal),
                ..
            }) => values.push(literal.clone()),
            other => {
                return Err(syn::Error::new_spanned(
                    other,
                    format!("`default_values` expects string literals, e.g. `{example}`"),
                ));
            }
        }
    }
    set_once(
        &mut raw.collection.default_values,
        (values, meta.path.span()),
        meta,
    )
}

fn bound(expr: &Expr) -> syn::Result<usize> {
    match expr {
        Expr::Lit(ExprLit {
            lit: Lit::Int(literal),
            ..
        }) => literal.base10_parse(),
        other => Err(syn::Error::new_spanned(
            other,
            "`num_args` bounds must be integer literals, e.g. `num_args = 1..`",
        )),
    }
}

fn range(range: &ExprRange) -> syn::Result<(usize, Option<usize>)> {
    let min = range.start.as_deref().map(bound).transpose()?.unwrap_or(0);
    let max = match (&range.end, &range.limits) {
        (None, _) => None,
        (Some(end), RangeLimits::Closed(_)) => Some(bound(end)?),
        (Some(end), RangeLimits::HalfOpen(_)) => match bound(end)?.checked_sub(1) {
            Some(max) => Some(max),
            None => {
                return Err(syn::Error::new_spanned(range, "`num_args` range is empty"));
            }
        },
    };
    match max {
        Some(max) if max < min => Err(syn::Error::new_spanned(range, "`num_args` range is empty")),
        Some(0) => Err(syn::Error::new_spanned(
            range,
            "`num_args` must accept at least one value",
        )),
        _ => Ok((min, max)),
    }
}

pub(super) fn num_args(meta: &ParseNestedMeta, raw: &mut RawField) -> syn::Result<()> {
    let expr: Expr = require_value(meta, "num_args", "num_args = 1..")?.parse()?;
    let value = match &expr {
        Expr::Range(expr) => range(expr)?,
        Expr::Lit(_) => match bound(&expr)? {
            0 => {
                return Err(syn::Error::new_spanned(
                    expr,
                    "`num_args` must accept at least one value",
                ));
            }
            count => (count, Some(count)),
        },
        other => {
            return Err(syn::Error::new_spanned(
                other,
                "`num_args` expects an integer or a range of integer literals, e.g. `num_args = 1..` or `num_args = 2..=4`",
            ));
        }
    };
    set_once(
        &mut raw.collection.num_args,
        (value, meta.path.span()),
        meta,
    )
}

pub(super) fn value_delimiter(meta: &ParseNestedMeta, raw: &mut RawField) -> syn::Result<()> {
    let delimiter =
        match require_value(meta, "value_delimiter", "value_delimiter = ','")?.parse::<Lit>()? {
            Lit::Char(literal) => (literal.value(), literal.span()),
            other => {
                return Err(syn::Error::new(
                    other.span(),
                    "`value_delimiter` expects a character literal, e.g. `value_delimiter = ','`",
                ));
            }
        };
    set_once(&mut raw.collection.value_delimiter, delimiter, meta)
}

pub(super) fn element<'t>(ty: &'t Type, errors: &mut Errors) -> Option<&'t Type> {
    if let Type::Array(array) = ty {
        errors.push(syn::Error::new_spanned(
            array,
            format!(
                "fixed-size array fields are not supported; collect repeated values into {SUPPORTED}"
            ),
        ));
        return None;
    }
    if let Some(inner) = option_inner_type(ty)
        && (collection_element(inner).is_some() || matches!(inner, Type::Array(_)))
    {
        errors.push(syn::Error::new_spanned(
            ty,
            "`Option<T>` cannot wrap a collection; a collection field is already empty when absent, so remove the `Option`",
        ));
        return None;
    }
    let element = collection_element(ty)?;
    if collection_element(element).is_some()
        || option_inner_type(element).is_some()
        || matches!(element, Type::Array(_))
    {
        errors.push(syn::Error::new_spanned(
            element,
            "collection elements cannot be collections, arrays or `Option<T>`",
        ));
        return None;
    }
    Some(element)
}

fn split(value: &str, delimiter: Option<char>) -> Vec<&str> {
    match delimiter {
        Some(delimiter) => value.split(delimiter).collect(),
        None => vec![value],
    }
}

fn check_default(
    literal: &LitStr,
    delimiter: Option<char>,
    possible_values: &[String],
    errors: &mut Errors,
) {
    if possible_values.is_empty() {
        return;
    }
    let value = literal.value();
    for piece in split(&value, delimiter) {
        if !possible_values.iter().any(|possible| possible == piece) {
            errors.push(syn::Error::new(
                literal.span(),
                format!(
                    "default value \"{piece}\" is not one of the possible values: {}",
                    possible_values.join(", ")
                ),
            ));
        }
    }
}

pub(super) fn build(
    raw: RawCollection,
    collection: bool,
    kind: &FieldKind,
    default: Option<&(DefaultSource, DefaultValue)>,
    possible_values: &[String],
    errors: &mut Errors,
) -> Option<CollectionModel> {
    if !collection {
        let keys = [
            ("default_values", raw.default_values.map(|(_, span)| span)),
            ("num_args", raw.num_args.map(|(_, span)| span)),
            ("value_delimiter", raw.value_delimiter.map(|(_, span)| span)),
        ];
        for (key, span) in keys {
            if let Some(span) = span {
                errors.push(syn::Error::new(
                    span,
                    format!(
                        "`{key}` requires a field collecting repeated values, of type {SUPPORTED}"
                    ),
                ));
            }
        }
        return None;
    }

    if let Some((_, span)) = raw.num_args
        && !matches!(kind, FieldKind::Positional)
    {
        errors.push(syn::Error::new(
            span,
            "`num_args` is only supported on positional arguments; a repeated option takes one value per occurrence",
        ));
    }

    let delimiter = raw.value_delimiter.map(|(delimiter, _)| delimiter);
    match (default, &raw.default_values) {
        (Some((DefaultSource::DefaultValueT, value)), _) => errors.push(syn::Error::new(
            value.span(),
            "`default_value_t` is not supported on collections; use `default_value` or `default_values`",
        )),
        (Some((source, _)), Some((_, span))) => errors.push(syn::Error::new(
            *span,
            format!(
                "conflicting default values: {} and `default_values` cannot be used together; use only one of them",
                source.describe()
            ),
        )),
        (Some((_, DefaultValue::Literal(literal))), None) => {
            check_default(literal, delimiter, possible_values, errors)
        }
        _ => {}
    }
    let default_values = raw
        .default_values
        .map(|(values, _)| values)
        .unwrap_or_default();
    for literal in &default_values {
        check_default(literal, None, possible_values, errors);
    }

    let (min, max) = raw.num_args.map_or((0, None), |(range, _)| range);
    Some(CollectionModel {
        delimiter,
        min,
        max,
        default_values,
    })
}

pub(in crate::model) fn validate_positionals(fields: &[FieldModel], errors: &mut Errors) {
    let mut collecting: Option<&FieldModel> = None;
    for field in fields {
        if !matches!(field.kind, FieldKind::Positional) || field.parsing.last {
            continue;
        }
        match collecting {
            Some(collecting) => errors.push(syn::Error::new(
                field.ident.span(),
                format!(
                    "positional argument `{}` cannot follow `{}`, which collects all remaining positional arguments; move it before `{}` or make it `last = true`",
                    field.name, collecting.name, collecting.name
                ),
            )),
            None if field.collection.is_some() => collecting = Some(field),
            None => {}
        }
    }
}