sigmd 0.1.0

Windows API signature metadata
Documentation
//! Lowering of [`clang::Type`] into the [`Type`] shape.

use sigmd::model::{Type, TypeKind};

use super::{BuildContext, clang};

/// Converts a [`clang::Type`] into the model [`Type`].
pub fn build_type(ty: clang::Type<'_>, ctx: &BuildContext) -> Type {
    let mut current = ty.get_canonical_type();
    let mut indirections = 0u8;

    while is_pointer_kind(current.get_kind()) {
        indirections = indirections.saturating_add(1);
        match current.get_pointee_type() {
            Some(pointee) => current = pointee.get_canonical_type(),
            None => break,
        }
    }

    let raw_name = current.get_display_name();
    let name = strip_modifiers(&raw_name).to_string();
    let kind = ctx
        .parse_custom_type(&name)
        .map_or_else(|| leaf_kind(current.get_kind()), TypeKind::Custom);

    Type {
        indirections,
        name,
        kind,
    }
}

fn is_pointer_kind(kind: clang::TypeKind) -> bool {
    matches!(
        kind,
        clang::TypeKind::Pointer
            | clang::TypeKind::LValueReference
            | clang::TypeKind::RValueReference,
    )
}

fn leaf_kind(kind: clang::TypeKind) -> TypeKind {
    match kind {
        clang::TypeKind::Void => TypeKind::Void,
        clang::TypeKind::Bool => TypeKind::Bool,
        clang::TypeKind::CharS | clang::TypeKind::CharU => TypeKind::Char8,
        clang::TypeKind::WChar => TypeKind::Char16,
        clang::TypeKind::SChar => TypeKind::I8,
        clang::TypeKind::Short => TypeKind::I16,
        clang::TypeKind::Int | clang::TypeKind::Long => TypeKind::I32,
        clang::TypeKind::LongLong => TypeKind::I64,
        clang::TypeKind::UChar => TypeKind::U8,
        clang::TypeKind::UShort => TypeKind::U16,
        clang::TypeKind::UInt | clang::TypeKind::ULong => TypeKind::U32,
        clang::TypeKind::ULongLong => TypeKind::U64,
        clang::TypeKind::Float => TypeKind::F32,
        clang::TypeKind::Double => TypeKind::F64,
        clang::TypeKind::Enum => TypeKind::I32,
        _ => TypeKind::Unknown,
    }
}

pub fn strip_modifiers(name: &str) -> &str {
    let name = name.strip_prefix("const ").unwrap_or(name);
    let name = name.strip_prefix("enum ").unwrap_or(name);
    let name = name.strip_prefix("struct ").unwrap_or(name);
    let name = name.strip_prefix("union ").unwrap_or(name);
    let name = name.strip_prefix("class ").unwrap_or(name);
    name.strip_suffix(" const").unwrap_or(name)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn strip_modifiers_removes_prefixes_and_suffix() {
        assert_eq!(strip_modifiers("DWORD"), "DWORD");
        assert_eq!(strip_modifiers("const DWORD"), "DWORD");
        assert_eq!(strip_modifiers("struct _UNICODE_STRING"), "_UNICODE_STRING");
        assert_eq!(strip_modifiers("enum FOO"), "FOO");
        assert_eq!(strip_modifiers("DWORD const"), "DWORD");
        // strips chain: "const " then "struct " -> "X".
        assert_eq!(strip_modifiers("const struct X"), "X");
    }
}