starlark_derive 0.14.2

Derive helpers for the starlark package.
Documentation
/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Proc macro for `#[type_matcher]` attribute.
//!
//! This attribute is placed on `impl TypeMatcher for X` blocks and generates:
//! 1. `unsafe impl TypeMatcherRegistered for X {}` - for both generic and non-generic types
//! 2. For non-generic types only: `register_avalue_simple_frozen!(TypeCompiledImplAsStarlarkValue<X>);`
//!
//! # Generic vs Non-Generic Types
//!
//! For **non-generic types** (e.g., `impl TypeMatcher for IsStr`):
//! - The macro generates both the `TypeMatcherRegistered` impl and vtable registration
//! - No additional work needed
//!
//! For **generic types** (e.g., `impl<T: TypeMatcher> TypeMatcher for IsListOf<T>`):
//! - The macro generates a blanket `TypeMatcherRegistered` impl
//! - Vtable registration is NOT generated (cannot register generic types)
//! - You must manually add `register_type_matcher!(IsListOf<ConcreteType>);` for each
//!   concrete instantiation used in the codebase
//!
//! # Finding Concrete Instantiations
//!
//! Run `arc rust-check` with `pagable` feature enabled. Errors like:
//!   "the trait bound `IsListOf<TypeMatcherBox>: TypeMatcherRegistered` is not satisfied"
//! indicate which concrete types need registration.
//!
//! Two cases in error messages:
//! - Concrete type shown (e.g., `IsListOf<IsStr>`) - register directly
//! - `impl TypeMatcher` shown - trace callers to find concrete type (usually `TypeMatcherBox`)

use proc_macro2::TokenStream;
use quote::quote;
use syn::spanned::Spanned;

fn derive_type_matcher_impl(_attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
    let item_impl: syn::ItemImpl = syn::parse2(input)?;

    // Verify this is an impl for TypeMatcher trait
    let trait_path = item_impl
        .trait_
        .as_ref()
        .map(|(_, path, _)| path)
        .ok_or_else(|| {
            syn::Error::new(
                item_impl.span(),
                "#[type_matcher] must be applied to an `impl TypeMatcher for ...` block",
            )
        })?;

    // Check that the trait is TypeMatcher (just check the last segment)
    let trait_name = trait_path
        .segments
        .last()
        .map(|seg| seg.ident.to_string())
        .unwrap_or_default();
    if trait_name != "TypeMatcher" {
        return Err(syn::Error::new(
            trait_path.span(),
            "#[type_matcher] must be applied to an `impl TypeMatcher for ...` block",
        ));
    }

    // Get the Self type
    let self_ty = &item_impl.self_ty;

    // Get the generics from the impl block
    let generics = &item_impl.generics;

    // Check if the impl block has generic type parameters (not just lifetimes)
    let has_type_params = generics.type_params().next().is_some();

    // Generic types are not supported - they require special handling for vtable registration
    if has_type_params {
        return Err(syn::Error::new(
            generics.span(),
            "#[type_matcher] does not support generic type parameters. \
             For generic TypeMatcher implementations: \
             1. Remove #[type_matcher] attribute from the impl block, \
             2. Run build or `arc rust-check` with `pagable` feature to find call sites, \
             3. Errors show types like `YourType<X>: TypeMatcherRegistered is not satisfied`. \
                Two cases for X: \
                a) Concrete type (e.g., `IsStr`, `IsNone`, `StarlarkTypeIdMatcher`) - use directly \
                b) `impl TypeMatcher` - trace CALLERS to find concrete type, usually `TypeMatcherBox` \
                   from `TypeMatcherBoxAlloc.ty(...)` \
             4. Add blanket unsafe impl: `unsafe impl<...> TypeMatcherRegistered for YourType<...> {}`, \
             5. Add `register_type_matcher!(YourType<ConcreteArg>);` for each concrete instantiation. \
             Example: IsListOf errors show IsListOf<IsStr> (case a) and IsListOf<impl TypeMatcher> (case b). \
             Register both: `register_type_matcher!(IsListOf<IsStr>);` \
                           `register_type_matcher!(IsListOf<TypeMatcherBox>);`",
        ));
    }

    // Generate TypeMatcherRegistered impl for both generic and non-generic types.
    let registered_impl = quote! {
        // SAFETY: This impl is generated by the #[type_matcher] macro.
        // For non-generic types, the vtable is also registered below.
        unsafe impl starlark::values::typing::TypeMatcherRegistered
            for #self_ty
        {}
    };

    // Generate vtable registration
    let vtable_registration = quote! {

        starlark::register_type_matcher!(#self_ty);
    };

    // Return the original impl block plus the generated code
    Ok(quote! {
        #item_impl
        #registered_impl
        #vtable_registration
    })
}

pub fn derive_type_matcher(
    attr: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    match derive_type_matcher_impl(attr.into(), input.into()) {
        Ok(tokens) => tokens.into(),
        Err(err) => err.to_compile_error().into(),
    }
}